Klant: Useq

Project: Organ atlas

Dataset: Kidney data

date: 14 41 Thu 06 October, 2022

loading dependencies Please make sure the following packages are installed and required libraries can be loaded: * install.packages(“pkgbuild”) // pkgbuild::check_build_tools() * install.packages(“devtools”) * devtools::install_github(“Nanostring-Biostats/NanoStringNCTools”) * devtools::install_github(“Nanostring-Biostats/GeomxTools”, ref = “dev”) * devtools::install_github(“Nanostring-Biostats/GeoMxWorkflows”, ref = “main”) * BiocManager::install(“GeoMxWorkflows”) * devtools::install_github(“DavisLaboratory/standR”) * BiocManager::install(“SpatialDecon”) * BiocManager::install(“GSVA”) * install.packages(“plotly”) * install.packages(“DT”) * install.packages(“msigdbr”) #install.packages(“digest”) #install.packages(“rmarkdown”) #install.packages(“kable”)

#load libraries
library(NanoStringNCTools)
library(GeomxTools)
library(GeoMxWorkflows)
library(SpatialDecon)
library(GSVA) #for pathway analyses
library(msigdbr) #for molecular signatures in pathway analyses
library(knitr)
library(dplyr)
library(ggforce)
library(ggplot2)
library(scales) # for percent
library(reshape2)  # for melt
library(cowplot)   # for plot_grid
library(umap)
library(Rtsne)
library(pheatmap)  # for pheatmap
library(ggrepel) 
library(scales) #for ggplot peaudolog to prevent errors on log(0)
library(DT)
library(plotly)
library(gridExtra)
library(RColorBrewer)

library(limma)
library(clusterProfiler) #pathway analysis
library(gage)

1 loading base files

# Reference the main folder 'file.path' containing the sub-folders with each data file type:

datadir<-file.path("C:/Users/pkloosterman/Documents/general_workflow/Kidney_Dataset")
#datadir<-file.path("C:/Users/pimkl/OneDrive/Documenten/UMCU/R/general_workflow/Kidney_Dataset/")

To locate a specific file path replace the above line with datadir <- file.path(“~/Folder/SubFolder/DataLocation”) replace the Folder, SubFolder, DataLocation as needed. The DataLocation folder should contain a dccs, pkcs, and annotation folder with each set of files present as needed automatically list files in each directory for use.

Take care you import a column with nuclei count separately if you want.

DCCFiles <- dir(file.path(datadir, "dccs"), pattern = ".dcc$",
                full.names = TRUE, recursive = TRUE)
PKCFiles <- dir(file.path(datadir, "pkcs"), pattern = ".pkc$",
                full.names = TRUE, recursive = TRUE)
SampleAnnotationFile <-
  dir(file.path(datadir, "annotation"), pattern = "^[^~]",
      full.names = TRUE, recursive = TRUE)

2 load data

Data <-
  readNanoStringGeoMxSet(dccFiles = DCCFiles,
                         pkcFiles = PKCFiles,
                         phenoDataFile = SampleAnnotationFile,
                         phenoDataSheet = "Template",
                         phenoDataDccColName = "Sample_ID",
                         protocolDataColNames = c("aoi", "roi"),
                         experimentDataColNames = c("panel"))

#save data to prevent loading time for retakes
saveData<-Data
#Data<-saveData

#change Data column names and manual correction of spelling errors
Data@phenoData@data[["slide_name"]]<-Data@phenoData@data[["slide name"]]
Data@phenoData@data[["slide name"]]<-  NULL
Data@phenoData@data[["ANN4"]]<-gsub("i", "", Data@phenoData@data[["ANN4"]])

#+1 references the slide name column
ann_size<-length(colnames(Data@phenoData@data)[grepl("ANN",colnames(Data@phenoData@data))])+1 
ann_names<-c(colnames(Data@phenoData@data)[grepl("ANN",colnames(Data@phenoData@data))],"slide_name")

# Feel free to change the order of which colors are appointed.
color<-c("#A349A4", "#FFFF33", "#E7298A", "#091833", "#1B9E77", "#D95F02", "#7570B3",  "#66A61E", "#E6AB02", "#8DD3C7", "#9F000F", "#BEBADA", "#FB8072", "#80B1D3", "#FDB462", "#B3DE69", "#FCCDE5", "#D9D9D9", "#BC80BD", "#CCEBC5", "#FFED6F", "#377EB8", "#984EA3", "#4DAF4A", "#FF71CE", "#FF7F00", "#A6CEE3", "#1F78B4", "#B2DF8A", "#33A02C", "#FB9A99", "#E31A1C", "#FDBF6F", "#CAB2D6", "#6A3D9A", "#FFFF99", "#B15928")
show_col(color)

# Use count and count_max to set the range of color needed for each column.
# With setNames() the color code is linked to each unique individual value of each column.
count=1
color_list = list()
for (ann in ann_names) {
  count_max = count+length(unique(Data@phenoData@data[[ann]]))-1
  color_list[[ann]]<-setNames(color[count:count_max], unique(Data@phenoData@data[[ann]]))
  count=count_max+1
}
color_list
## $ANN1
##       DKD    normal 
## "#A349A4" "#FFFF33" 
## 
## $ANN2
##  disease3  disease4   normal3   normal4 disease1B disease2B  normal2B 
## "#E7298A" "#091833" "#1B9E77" "#D95F02" "#7570B3" "#66A61E" "#E6AB02" 
## 
## $ANN3
## glomerulus     tubule 
##  "#8DD3C7"  "#9F000F" 
## 
## $ANN4
##  abnormal        NA    normal 
## "#BEBADA" "#FB8072" "#80B1D3" 
## 
## $slide_name
##  disease3  disease4   normal3   normal4 disease1B disease2B  normal2B 
## "#FDB462" "#B3DE69" "#FCCDE5" "#D9D9D9" "#BC80BD" "#CCEBC5" "#FFED6F"
paste("Reads from following runs used: ",unique(pData(protocolData(Data))$SeqSetId))
## [1] "Reads from following runs used:  "

3 Study design

pkcs <- annotation(Data)
modules <- gsub(".pkc", "", pkcs)
kable(data.frame(PKCs = pkcs, modules = modules))
PKCs modules
TAP_H_WTA_v1.0.pkc TAP_H_WTA_v1.0

Select the annotations we want to show, use `` to surround column names with spaces or special symbols

count_mat <- dplyr::count(pData(Data), ANN1,ANN2,ANN3,ANN4,slide_name)

Simplify the slide names if required

count_mat$slide_name <- gsub("disease", "d", gsub("normal", "n", count_mat$slide_name))
count_mat$ANN2 <- gsub("disease", "d", gsub("normal", "n", count_mat$ANN2))
#count_mat$path_ann <- gsub("i", "", count_mat$path_ann) #correcting spelling error

Gather the data and plot in order: class, slide name, region, segment

test_gr <- gather_set_data(count_mat, 1:ann_size)
test_gr$x <- factor(test_gr$x, levels = ann_names)

Plot Sankey

ggplot(test_gr, height = 10, width = 10, aes(x, id = id, split = y, value = n)) +
  geom_parallel_sets(aes(fill = ANN3), alpha = 0.5, axis.width = 0.1) +
  geom_parallel_sets_axes(axis.width = 0.2) +
  geom_parallel_sets_labels(color = "white", size = 5) +
  theme_classic(base_size = 12) + 
  theme(legend.position = "bottom",
        axis.ticks.y = element_blank(),
        axis.line = element_blank(),
        axis.text.y = element_blank()) +
  scale_y_continuous(expand = expansion(0)) + 
  scale_x_discrete(expand = expansion(0)) +
  labs(x = "", y = "") +
  scale_fill_manual(values=color_list$ANN3) +
  annotate(geom = "segment", x = 4.25, xend = 4.25,
           y = 10, yend = 61, lwd = 2) +
  annotate(geom = "text", x = 4.19, y = 25, angle = 90, size = 5,
           hjust = 0.5, label = "50 segments")

4 QC & Pre-processing

Shift counts to one

#shift any expression counts with a value of 0 to 1 to enable in downstream transformations.
Data <- shiftCountsOne(Data, useDALogic = TRUE)

4.1 Segment QC

We first assess sequencing quality and adequate tissue sampling for every ROI/AOI segment.

Every ROI/AOI segment will be tested for:

Raw sequencing reads: segments with >1000 raw reads are removed. % Aligned,% Trimmed, or % Stitched sequencing reads: segments below ~80% for one or more of these QC parameters are removed. % Sequencing saturation ([1-deduplicated reads/aligned reads]%): segments below ~50% require additional sequencing to capture full sample diversity and are not typically analyzed until improved. Negative Count: this is the geometric mean of the several unique negative probes in the GeoMx panel that do not target mRNA and establish the background count level per segment; segments with low negative counts (1-10) are not necessarily removed but may be studied closer for low endogenous gene signal and/or insufficient tissue sampling. No Template Control (NTC) count: values >1,000 could indicate contamination for the segments associated with this NTC; however, in cases where the NTC count is between 1,000- 10,000, the segments may be used if the NTC data is uniformly low (e.g. 0-2 counts for all probes). Nuclei: >100 nuclei per segment is generally recommended; however, this cutoff is highly study/tissue dependent and may need to be reduced; what is most important is consistency in the nuclei distribution for segments within the study. Area: generally correlates with nuclei; a strict cutoff is not generally applied based on area.

4.1.1 Select Segment QC

First, we select the QC parameter cutoffs, against which our ROI/AOI segments will be tested and flagged appropriately. We have selected the appropriate study-specific parameters for this study. Note: the default QC values recommended above are advised when surveying a new dataset for the first time.

Default QC cutoffs are commented in () adjacent to the respective parameters study-specific values were selected after visualizing the QC results in more detail below

QC_params <-
  list(minSegmentReads = 1000, # Minimum number of reads (1000)
       percentTrimmed = 80,    # Minimum % of reads trimmed (80%)
       percentStitched = 80,   # Minimum % of reads stitched (80%)
       percentAligned = 75,    # Minimum % of reads aligned (80%)
       percentSaturation = 50, # Minimum sequencing saturation (50%)
       minNegativeCount = 1,   # Minimum negative control counts (10)
       maxNTCCount = 9000,     # Maximum counts observed in NTC well (1000)
       minNuclei = 20,        # Minimum # of nuclei estimated (100)
       minArea = 1000)         # Minimum segment area (5000)
Data <-
  setSegmentQCFlags(Data, qcCutoffs = QC_params)        

cat("pre-QC features:", dim(Data)[1], "\npre-QC samples:", dim(Data)[2])
## pre-QC features: 18642 
## pre-QC samples: 276
#Table for clarification
QCparams_df <- data.frame (
  items = c("minSegmentReads","percentTrimmed","percentStitched","percentAligned","percentSaturation",
            "minNegativeCount","maxNTCCount","minNuclei","minArea"),
  defaults = c(1000,80,80,80,50,10,1000,100,5000),
  actual = c(QC_params$minSegmentReads,QC_params$percentTrimmed,QC_params$percentStitched,QC_params$percentAligned,QC_params$percentSaturation,QC_params$minNegativeCount,QC_params$maxNTCCount,QC_params$minNuclei,QC_params$minArea)
)

datatable(QCparams_df, rownames=FALSE,
          caption = "QC thresholds",
          extensions = 'Buttons', options = list (
            dom = 'Bftrip',
            buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
          )
)

Collate QC Results

QCResults <- protocolData(Data)[["QCFlags"]]
flag_columns <- colnames(QCResults)
QC_Summary <- data.frame(Pass = colSums(!QCResults[, flag_columns]),
                         Warning = colSums(QCResults[, flag_columns]))

QCResults$QCStatus <- apply(QCResults, 1L, function(x) {
  ifelse(sum(x) == 0L, "PASS", "WARNING")
})

QC_Summary["TOTAL FLAGS", ] <-
  c(sum(QCResults[, "QCStatus"] == "PASS"),
    sum(QCResults[, "QCStatus"] == "WARNING"))

col_by <- "ANN1"
col_by_plate <- "Plate_ID"

4.2 Graphical summaries of QC statistics

Use the tab-menu to navigate!

QC_histogram <- function(assay_data = NULL,
                         annotation = NULL,
                         fill_by = NULL,
                         thr = NULL,
                         scale_trans = NULL) {
  plt <- ggplot(assay_data,
                aes_string(x = paste0("unlist(`", annotation, "`)"),
                           fill = fill_by)) +
    geom_histogram(bins = 50) +
    geom_vline(xintercept = thr, lty = "dashed", color = "black") +
    theme_bw() + guides(fill = "none") +
    facet_wrap(as.formula(paste("~", fill_by)), nrow = 4) +
    scale_fill_manual(values=color_list$ANN1) +
    labs(x = annotation, y = "segments, #", title = annotation)
  if(!is.null(scale_trans)) {
    plt <- plt +
      scale_x_continuous(trans = scale_trans)
  }
  plt
}

Trimmed

QC_histogram(sData(Data), "Trimmed (%)", col_by, QC_params$percentTrimmed)

Stiched (%)

QC_histogram(sData(Data), "Stitched (%)", col_by, QC_params$percentStitched)

Aligned (%)

QC_histogram(sData(Data), "Aligned (%)", col_by,QC_params$percentAligned)

Sequencing Saturation (%)

QC_histogram(sData(Data), "Saturated (%)", col_by, QC_params$percentSaturation) +
  labs(title = "Sequencing Saturation (%)",
       x = "Sequencing Saturation (%)")

Area

QC_histogram(sData(Data), "area", col_by, QC_params$minArea, scale_trans = "log10")

Nuclei count

QC_histogram(sData(Data), "nuclei", col_by, QC_params$minNuclei)

DuplicationRate

ggplot(pData(protocolData(Data)),
       aes(x = Plate_ID, fill=Plate_ID,
          y = (DeduplicatedReads/Raw))) +
  geom_violin() +
  geom_jitter(width = .2) +
  labs(y = "Deduplicated / Raw reads") +
  scale_y_continuous(labels = scales::percent) +
  theme_bw()

# Plate_ID to check the low dub/raw count
QC_histogram(sData(Data), "Saturated (%)", col_by_plate, QC_params$percentSaturation) +
  labs(title = "Sequencing Saturation (%)",
       x = "Sequencing Saturation (%)")

Negprobes vs Endogenous

tmp_target_Data <- aggregateCounts(Data)

#get negative probe data
negs<-subset(tmp_target_Data,CodeClass=="Negative")

p1<-ggplot(pData(negs),
       aes(x = ANN1, fill = ANN1,
          y = assayDataElement(negs, elt = "exprs"))) +
  geom_violin() +
  geom_jitter(width = .2) +
  labs(y = "Negative probes Expression") +
  scale_y_continuous(limits = c(1,3000), trans = "log2") +
  theme_bw()


# get endogenous probe data
end<-subset(tmp_target_Data,CodeClass=="Endogenous")

p2<-ggplot(pData(end),
       aes(x = ANN1, fill = ANN1,
           y = colMeans(assayDataElement(end, elt = "exprs")))) +
  geom_violin() +
  geom_jitter(width = .2) +
  labs(y = "Endogenous probes Expression (mean)") +
  scale_y_continuous(limits = c(1,3000),trans = "log2") +
  theme_bw()

pl <-list(p1,p2)
plot_grid(plotlist=pl, nrow=1, align='h')

Neg_probe reads compared to raw_reads

# make background total neg probe count
fdata_df<-fData(Data)
negprobesnames<-rownames(fdata_df[fdata_df$Negative==TRUE,])
temp_exp<-assayDataElement(Data,elt='exprs')
negprobe_expr_fd<-temp_exp[rownames(temp_exp) %in% negprobesnames,]
tot_neg_ctrl_reads<-colSums(negprobe_expr_fd)
tot_dedup_reads<-pData(protocolData(Data))$DeduplicatedReads

df<-data.frame('aoi'= names(tot_neg_ctrl_reads),'tot_dedup_reads' = as.numeric(tot_dedup_reads),'tot_neg_ctrl_reads'=as.numeric(tot_neg_ctrl_reads))
df<-melt(df,id="aoi")
ggplot(df,aes(fill=variable,y=value,x=aoi)) + 
  geom_bar(position="identity",stat="identity") +
  scale_y_continuous(trans = log2_trans()) +
  theme(legend.position="bottom",axis.text.x = element_blank(),axis.ticks.x=element_blank())                     

Duplicated reads vs Background

# get dcc per plate. sum negprobe counts/dcc/plate
ggplot(pData(protocolData(Data)),
       aes(x = Plate_ID, fill=Plate_ID,
          y = DeduplicatedReads)) +
  geom_violin() +
  geom_jitter(width = .2) +
  labs(y = "Deduplicated / Raw reads") +
  scale_y_log10()+
  geom_hline(data =pData(protocolData(Data)) , 
           aes(yintercept = NTC, colour=Plate_ID)) +
  theme_bw()

Duplicated reads vs ROIarea

temp_df<-cbind(pData(Data),pData(protocolData(Data)),dcc=rownames(pData(Data)))

ggplot(temp_df,
       aes(x = dcc, colour=slide_name,
          y = (DeduplicatedReads/area) )) +
  geom_point() +
  ylim(0,20) + 
  labs(y = "Deduplicated reads / ROI area") +
  theme(axis.text.x = element_text(size =6, angle=90, hjust=1) )

Duplicated reads vs nuclei

temp_df<-cbind(pData(Data),pData(protocolData(Data)),dcc=rownames(pData(Data)))

ggplot(temp_df,
       aes(x = dcc, colour=slide_name,
          y = (DeduplicatedReads/nuclei) )) +
  geom_point() +
  ylim(0,200) +
  labs(y = "Deduplicated reads / nuclei") +
  theme(axis.text.x = element_text(size =6, angle=90, hjust=1) )

4.3 Process Negative GeoMeans

# Calculate the negative geometric means for each module
# It will show only the negative probes geomean, so expect less segments.
negativeGeoMeans <- 
  esBy(negativeControlSubset(Data), 
       GROUP = "Module", 
       FUN = function(x) { 
         assayDataApply(x, MARGIN = 2, FUN = ngeoMean, elt = "exprs") 
       }) 
protocolData(Data)[["NegGeoMean"]] <- negativeGeoMeans

negCols <- paste0("NegGeoMean_", modules)
pData(Data)[, negCols] <- sData(Data)[["NegGeoMean"]]
for(ann in negCols) {
  plt <- QC_histogram(pData(Data), ann, col_by, 2, scale_trans = "log10")
  print(plt)
}

# Detatch neg_geomean columns ahead of aggregateCounts call

pData(Data) <- pData(Data)[, !colnames(pData(Data)) %in% negCols]

Show all NTC values, Freq = # of Segments with a given NTC count:

QC<-sData(Data)

ntc_df <-QC[,c("slide_name","Plate_ID","NTC_ID","NTC")]
temptable<-ntc_df %>% dplyr::count(ntc_df$slide_name,ntc_df$NTC_ID,ntc_df$Plate_ID,ntc_df$NTC)
colnames(temptable) <- c("Slide_name","NTC_ID","Plate_ID","NTC_count","Number_of_samples")
datatable(temptable, rownames = FALSE)
kable(table(NTC_Count = sData(Data)$NTC), col.names = c("NTC Count", "# of Segments"))
NTC Count # of Segments
3 64
113 71
397 47
8704 94
kable(QC_Summary, caption = "QC Summary Table for each Segment")
QC Summary Table for each Segment
Pass Warning
LowReads 272 4
LowTrimmed 276 0
LowStitched 273 3
LowAligned 266 10
LowSaturation 272 4
LowNegatives 276 0
HighNTC 276 0
LowNuclei 276 0
LowArea 265 11
TOTAL FLAGS 259 17
datatable(QC_Summary,
          caption = "AOI QC Summary",
          extensions = 'Buttons', options = list (
            dom = 'Bftrip',
            buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
          )
)

Show AOIs which fail critical QCs.

QC<-sData(Data)
undersat<-subset(QC, `Saturated (%)`<= QC_params$percentSaturation)

if(nrow(undersat)> 0) {

datatable(aggregate(undersat, by=list(undersat$SampleID),paste,collapse=";"),
          extensions = 'Buttons', options = list (
            dom = 'Bftrip',
            buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
          )
)}

Subsetting our dataset has removed samples which did not pass QC

Data <- Data[, QCResults$QCStatus == "PASS"]

Generally keep the qcCutoffs parameters unchanged. Set removeLocalOutliers to FALSE if you do not want to remove local outliers

Data <- setBioProbeQCFlags(Data, 
                               qcCutoffs = list(minProbeRatio = 0.1,
                                                percentFailGrubbs = 20), 
                               removeLocalOutliers = FALSE)

ProbeQCResults <- fData(Data)[["QCFlags"]]

Define QC table for Probe QC

qc_df <- data.frame(Passed = sum(rowSums(ProbeQCResults[, -1]) == 0),
                    Global = sum(ProbeQCResults$GlobalGrubbsOutlier),
                    Local = sum(rowSums(ProbeQCResults[, -2:-1]) > 0
                                & !ProbeQCResults$GlobalGrubbsOutlier))

Subset object to exclude all that did not pass Ratio & Global testing

ProbeQCPassed <- 
  subset(Data, 
         fData(Data)[["QCFlags"]][,c("LowProbeRatio")] == FALSE &
           fData(Data)[["QCFlags"]][,c("GlobalGrubbsOutlier")] == FALSE)

Data <- ProbeQCPassed 
cat("After QC features:", dim(Data)[1], "\nAfter QC samples:", dim(Data)[2])
## After QC features: 18641 
## After QC samples: 259

Check how many unique targets the object has

length(unique(featureData(Data)[["TargetName"]]))
## [1] 18504

Collapse to targets

target_Data <- aggregateCounts(Data)

exprs(target_Data)[1:5, 1:2]
##       DSP-1001250007851-H-A02.dcc DSP-1001250007851-H-A03.dcc
## A2M                           485                         262
## NAT2                           15                          18
## ACADM                          31                          15
## ACADS                          27                          17
## ACAT1                          29                          24

Define LOQ SD threshold and minimum value

cutoff <- 2
minLOQ <- 2

4.4 Limit of Quantification

We define a limit of quantification (LOQ) per ROI/AOI segment based on the negative control probes to guide the filtering of segments and genes with low signal relative to background. The formula for calculating the LOQ in the \(i^{th}\) segment at \(n\) standard deviations (\(n = 2\) for this study) is: \(LOQ_i=geomean(NegProbe_i)*geoSD(NegProbe_i)^n\)

Calculate LOQ per module tested

LOQ <- data.frame(row.names = colnames(target_Data))
for(module in modules) {
  vars <- paste0(c("NegGeoMean_", "NegGeoSD_"),
                 module)
  if(all(vars[1:2] %in% colnames(pData(target_Data)))) {
    LOQ[, module] <-
      pmax(minLOQ,
           pData(target_Data)[, vars[1]] * 
             pData(target_Data)[, vars[2]] ^ cutoff)
  }
}
pData(target_Data)$LOQ <- LOQ

4.5 Filtering

After determining the limit of quantification (LOQ) per segment, we recommend filtering out either segments and/or genes with abnormally low signal. Filtering is an important step to focus on the true biological data of interest.

We determine the number of genes detected in each segment across the dataset.

LOQ_Mat <- c()
for(module in modules) {
  ind <- fData(target_Data)$Module == module
  Mat_i <- t(esApply(target_Data[ind, ], MARGIN = 1,
                     FUN = function(x) {
                       x > LOQ[, module]
                     }))
  LOQ_Mat <- rbind(LOQ_Mat, Mat_i)
}
# ensure ordering since this is stored outside of the geomxSet
LOQ_Mat <- LOQ_Mat[fData(target_Data)$TargetName, ]

4.5.1 Segment Gene Detection

We first filter out segments with exceptionally low signal. These segments will have a small fraction of panel genes detected above the LOQ relative to the other segments in the study. Let’s visualize the distribution of segments with respect to their % genes detected:

Save detection rate information to pheno data

pData(target_Data)$GenesDetected <- 
  colSums(LOQ_Mat, na.rm = TRUE)
pData(target_Data)$GeneDetectionRate <-
  pData(target_Data)$GenesDetected / nrow(target_Data)

Determine detection thresholds: 1%, 5%, 10%, 15%, >15%

pData(target_Data)$DetectionThreshold <- 
  cut(pData(target_Data)$GeneDetectionRate,
      breaks = c(0, 0.01, 0.05, 0.1, 0.15, 0.2,1),
      labels = c("<1%", "1-5%", "5-10%", "10-15%", "15-20%", ">20%"))

# stacked bar plot of different cut points (1%, 5%, 10%, 15%)
ggplot(pData(target_Data),
       aes(x = DetectionThreshold)) +
  geom_bar(aes(fill = ANN1)) +
  geom_text(stat = "count", aes(label = ..count..), vjust = -0.5) +
  theme_bw() +
  scale_y_continuous(expand = expansion(mult = c(0, 0.1))) +
  scale_fill_manual(values=color_list$ANN1) +
  labs(x = "Gene Detection Rate",
       y = "Segments, #",
       fill = "Segment Type")

cut percent genes detected at 1, 5, 10, 15

kable(table(pData(target_Data)$DetectionThreshold,
            pData(target_Data)$ANN1))
DKD normal
<1% 0 1
1-5% 1 6
5-10% 12 6
10-15% 28 5
15-20% 22 15
>20% 84 79
# set threshold for detectionlevel
# default 0.1
gene_det_threshold <- 0.05

target_Data <-
  target_Data[, pData(target_Data)$GeneDetectionRate >= gene_det_threshold]

dim(target_Data)
## Features  Samples 
##    18504      251

4.5.2 collect annotations

# **Select the annotations we want to show, use `` to surround column names with spaces or special symbols**
old_count_mat<-count_mat
count_mat <- dplyr::count(pData(Data), ANN1,ANN2,ANN3,ANN4,slide_name)

# simplify_slide_names if needed
#count_mat$slide_name <- gsub("disease", "d", gsub("normal", "n", count_mat$slide_name))

# gather the data and plot in order: class, slide name, region, segment
test_gr <- gather_set_data(count_mat, 1:ann_size)
test_gr$x <- 
  factor(test_gr$x, 
         levels = ann_names)


aoilist <-names(as.data.frame(assayDataElement(target_Data, elt= "exprs")))

ANN1 <-as.data.frame(pData(target_Data)$ANN1,unique(count_mat$ANN1))
colnames(ANN1) <- "class"
row.names(ANN1) <-aoilist

ANN2 <-as.data.frame(pData(target_Data)$ANN2,unique(count_mat$ANN2))
colnames(ANN2) <- "slide_ann"
row.names(ANN2) <-aoilist

ANN3 <-as.data.frame(pData(target_Data)$ANN3,unique(count_mat$ANN3))
colnames(ANN3) <- "region"
row.names(ANN3) <-aoilist

ANN4 <-as.data.frame(pData(target_Data)$ANN4,unique(count_mat$ANN4))
colnames(ANN4) <- "path"
row.names(ANN4) <-aoilist

SN <-as.data.frame(pData(target_Data)$slide_name, unique(count_mat$slide_name))
colnames(SN) <- "slide_name"
row.names(SN) <-aoilist

ann<-cbind(ANN1,ANN2,ANN3,ANN4,SN)

4.6 Manual removal of samples/classes

active_aois<-rownames(ann)

re-Collect annotations

# gather the data and plot in order: class, slide name, region, segment
test_gr <- gather_set_data(count_mat, 1:ann_size)
test_gr$x <-
  factor(test_gr$x,
         levels = ann_names)

re-Plot Sankey

ggplot(test_gr, aes(x, id = id, split = y, value = n)) +
  geom_parallel_sets(aes(fill = ANN1), alpha = 0.5, axis.width = 0.1) +
  geom_parallel_sets_axes(axis.width = 0.2) +
  geom_parallel_sets_labels(color = "white", size = 5) +
  theme_classic(base_size = 17) + 
  theme(legend.position = "bottom",
        axis.ticks.y = element_blank(),
        axis.line = element_blank(),
        axis.text.y = element_blank()) +
  scale_y_continuous(expand = expansion(0)) + 
  scale_x_discrete(expand = expansion(0)) +
  scale_fill_manual(values=color_list$ANN1) +
  labs(x = "", y = "") +
  annotate(geom = "segment", x = 3.25, xend = 3.25, y = 10, 
           yend = 60, lwd = 2) +
  annotate(geom = "text", x = 3.19, y = 25, angle = 90, size = 5,
           hjust = 0.5, label = "50 segments")

4.7 Gene Detection Rate

Calculate detection rate

LOQ_Mat <- LOQ_Mat[, colnames(target_Data)]
fData(target_Data)$DetectedSegments <- rowSums(LOQ_Mat, na.rm = TRUE)
fData(target_Data)$DetectionRate <-
  fData(target_Data)$DetectedSegments / nrow(pData(target_Data))

Gene of interest detection table

goi <- c("PDCD1", "CD274", "IFNG", "CD8A", "CD68", "EPCAM",
         "KRT18", "NPHS1", "NPHS2", "CALB1", "CLDN8")
goi_df <- data.frame(
  Gene = goi,
  Number = fData(target_Data)[goi, "DetectedSegments"],
  DetectionRate = percent(fData(target_Data)[goi, "DetectionRate"]))

4.8 Gene Filtering

We will graph the total number of genes detected in different percentages of segments. Based on the visualization below, we can better understand global gene detection in our study and select how many low detected genes to filter out of the dataset. Gene filtering increases performance of downstream statistical tests and improves interpretation of true biological signal.

Plot detection rate

plot_detect <- data.frame(Freq = c(1, 5, 10, 20, 30, 50))
plot_detect$Number <-
  unlist(lapply(c(0.01, 0.05, 0.1, 0.2, 0.3, 0.5),
                function(x) {sum(fData(target_Data)$DetectionRate >= x)}))
plot_detect$Rate <- plot_detect$Number / nrow(fData(target_Data))
rownames(plot_detect) <- plot_detect$Freq

ggplot(plot_detect, aes(x = as.factor(Freq), y = Rate, fill = Rate)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label = formatC(Number, format = "d", big.mark = ",")),
            vjust = 1.6, color = "black", size = 4) +
  scale_fill_gradient2(low = "orange2", mid = "lightblue",
                       high = "dodgerblue3", midpoint = 0.65,
                       limits = c(0,1),
                       labels = scales::percent) +
  theme_bw() +
  scale_y_continuous(labels = scales::percent, limits = c(0,1),
                     expand = expansion(mult = c(0, 0))) +
  labs(x = "% of Segments",
       y = "Genes Detected, % of Panel > LOQ")

Subset to target genes detected in at least 10% of the samples. Also manually include the negative control probe, for downstream use

# default=0.1
negativeProbefData <- subset(fData(target_Data), CodeClass == "Negative")
neg_probes <- unique(negativeProbefData$TargetName)
target_Data <- 
  target_Data[fData(target_Data)$DetectionRate >= 0.05 |
                    fData(target_Data)$TargetName %in% neg_probes, ]

# retain only detected genes of interest
goi <- goi[goi %in% rownames(target_Data)]

5 Normalization

We will now normalize the GeoMx data for downstream visualizations and differential expression. The two common methods for normalization of DSP-NGS RNA data are i) quartile 3 (Q3) or ii) background normalization.

Both of these normalization methods estimate a normalization factor per segment to bring the segment data distributions together. More advanced methods for normalization and modeling are under active development. However, for most studies, these methods are sufficient for understanding differences between biological classes of segments and samples.

Q3 normalization is typically the preferred normalization strategy for most DSP-NGS RNA studies. Given the low negative probe counts in this particular dataset as shown during Segment QC, we would further avoid background normalization as it may be less stable.

Before normalization, we will explore the relationship between the upper quartile (Q3) of the counts in each segment with the geometric mean of the negative control probes in the data. Ideally, there should be a separation between these two values to ensure we have stable measure of Q3 signal. If you do not see sufficient separation between these values, you may consider more aggressive filtering of low signal segments/genes.

Graph Q3 value vs negGeoMean of Negatives

ann_of_interest <- "ANN3"
Stat_data <- 
  data.frame(row.names = colnames(exprs(target_Data)),
             Segment = colnames(exprs(target_Data)),
             Annotation = pData(target_Data)[, ann_of_interest],
             Q3 = unlist(apply(exprs(target_Data), 2,
                               quantile, 0.75, na.rm = TRUE)),
             NegProbe = exprs(target_Data)[neg_probes, ])
Stat_data_m <- melt(Stat_data, measure.vars = c("Q3", "NegProbe"),
                    variable.name = "Statistic", value.name = "Value")

plt1 <- ggplot(Stat_data_m,
               aes(x = Value, fill = Statistic)) +
  geom_histogram(bins = 40) + theme_bw() +
  scale_x_continuous(trans = "log2") +
  facet_wrap(~Annotation, nrow = 1) + 
  scale_fill_brewer(palette = 3, type = "qual") +
  labs(x = "Counts", y = "Segments, #")

plt2 <- ggplot(Stat_data,
               aes(x = NegProbe, y = Q3, color = Annotation)) +
  geom_abline(intercept = 0, slope = 1, lty = "dashed", color = "darkgray") +
  geom_point() + guides(color = "none") + theme_bw() +
  scale_x_continuous(trans = "log2") + 
  scale_y_continuous(trans = "log2") +
  theme(aspect.ratio = 1) +
  labs(x = "Negative Probe GeoMean, Counts", y = "Q3 Value, Counts")

plt3 <- ggplot(Stat_data,
               aes(x = NegProbe, y = Q3 / NegProbe, color = Annotation)) +
  geom_hline(yintercept = 1, lty = "dashed", color = "darkgray") +
  geom_point() + theme_bw() +
  scale_x_continuous(trans = "log2") + 
  scale_y_continuous(trans = "log2") +
  theme(aspect.ratio = 1) +
  labs(x = "Negative Probe GeoMean, Counts", y = "Q3/NegProbe Value, Counts")

btm_row <- plot_grid(plt2, plt3, nrow = 1, labels = c("B", ""),
                     rel_widths = c(0.43,0.57))
plot_grid(plt1, btm_row, ncol = 1, labels = c("A", ""))

Q3 norm (75th percentile) for WTA/CTA with or without custom spike-ins

target_Data <- normalize(target_Data ,
                             norm_method = "quant", 
                             desiredQuantile = .75,
                             toElt = "q_norm")
#, data_type = "RNA" depricated after 4.1

Background normalization for WTA/CTA without custom spike-in

target_Data <- normalize(target_Data,
                             norm_method = "neg", 
                             fromElt = "exprs",
                             toElt = "neg_norm")

# , data_type = "RNA" depricated after 4.1

5.1 Visualize the first 10 segments with each normalization method

Use the tab-menu to navigate!

#Fix zero values, which go to -inf in log transform in standard boxplot
# temp <-as.matrix(exprs((target_Data)[,1:10]))
# long <- melt(temp)
# colnames(long) <- c("gene","segment","count")
# ggplot(long, aes(x=segment,y=count)) +
#     geom_boxplot(fill="#9EDAE5") +
#     scale_y_continuous(trans=scales::pseudo_log_trans(base = 10)) +
#     scale_x_discrete(labels=c(1:10)) +
#     labs(title="Raw counts", x="segment", y = "Counts, Raw")
# 
# 
# temp <-as.matrix(assayDataElement(target_Data[,1:10], elt = "q_norm"))
# long <- melt(temp)
# colnames(long) <- c("gene","segment","count")
# ggplot(long, aes(x=segment,y=count)) +
#     geom_boxplot(fill = "#2CA02C") +
#     scale_y_continuous(trans=scales::pseudo_log_trans(base = 10)) +
#     scale_x_discrete(labels=c(1:10)) +
#     labs(title="Q3 Norm Counts", x="segment", y = "Counts, Q3 Normalized")
# 
# 
# temp <-as.matrix(assayDataElement(target_Data[,1:10], elt = "neg_norm"))
# long <- melt(temp)
# colnames(long) <- c("gene","segment","count")
# ggplot(long, aes(x=segment,y=count)) +
#     geom_boxplot(fill = "#FF7F0E") +
#     scale_y_continuous(trans=scales::pseudo_log_trans(base = 10)) +
#     scale_x_discrete(labels=c(1:10)) +
#     labs(title="Neg Norm Counts", x="segment", y = "Counts, Neg. Normalized")

raw counts

boxplot(exprs(target_Data)[,1:8],
        col = "#9EDAE5", main = "Raw Counts",
        log = "y", names = 1:8, xlab = "Segment",
        ylab = "Counts, Raw")

Q3 normalized

boxplot(assayDataElement(target_Data[,1:8], elt = "q_norm"),
        col = "#2CA02C", main = "Q3 Norm Counts",
        log = "y", names = 1:8, xlab = "Segment",
        ylab = "Counts, Q3 Normalized")

Negative probe normalization

boxplot(assayDataElement(target_Data[,1:8], elt = "neg_norm"),
        col = "#FF7F0E", main = "Neg Norm Counts",
        log = "y", names = 1:8, xlab = "Segment",
        ylab = "Counts, Neg. Normalized")

6 Unsupervised Analysis

6.1 UMAP

Use the tab-menu to navigate!

1

gc()
##            used  (Mb) gc trigger   (Mb)  max used   (Mb)
## Ncells 10406983 555.8   28883698 1542.6  28883698 1542.6
## Vcells 69398283 529.5  114624478  874.6 114622516  874.6
custom_umap <- umap::umap.defaults
custom_umap$random_state <- 42
# run UMAP

umap_out <-
  umap(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
       config = custom_umap)
pData(target_Data)[, c("UMAP1", "UMAP2")] <- umap_out$layout[, c(1,2)]
ggplot(pData(target_Data),
       aes(x = UMAP1, y = UMAP2, color = ANN1, shape = slide_name)) +

  geom_point(size = 3) +
  #geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
  theme_bw()

2

umap_out <-
  umap(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
       config = custom_umap)
pData(target_Data)[, c("UMAP1", "UMAP2")] <- umap_out$layout[, c(1,2)]
ggplot(pData(target_Data),
       aes(x = UMAP1, y = UMAP2, color = ANN2, shape = ANN1)) +
  geom_point(size = 3) +
  #geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
  theme_bw()

3

umap_out <-
  umap(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
       config = custom_umap)
pData(target_Data)[, c("UMAP1", "UMAP2")] <- umap_out$layout[, c(1,2)]
ggplot(pData(target_Data),
       aes(x = UMAP1, y = UMAP2, color = ANN3, shape = ANN2)) +
  geom_point(size = 3) +
  #geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
  theme_bw()

6.1 Run tSNE

Use the tab-menu to navigate!

One common approach to understanding high-plex data is dimension reduction. Two common methods are UMAP and tSNE, which are non-orthogonally constrained projections that cluster samples based on overall gene expression. In this study, we see by either UMAP (from the umap package) or tSNE (from the Rtsne package)

1

set.seed(42) # set the seed for tSNE as well

tsne_out <-
  Rtsne(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
        perplexity = ncol(target_Data)*.15)
pData(target_Data)[, c("tSNE1", "tSNE2")] <- tsne_out$Y[, c(1,2)]
ggplot(pData(target_Data),
       aes(x = tSNE1, y = tSNE2, shape = ANN3, color = ANN1)) +
  geom_point(size = 3) +
  geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
  theme_bw()

2

tsne_out <-
  Rtsne(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
        perplexity = ncol(target_Data)*.15)
pData(target_Data)[, c("tSNE1", "tSNE2")] <- tsne_out$Y[, c(1,2)]
ggplot(pData(target_Data),
       aes(x = tSNE1, y = tSNE2, color = ANN2, shape = ANN1)) +
  geom_point(size = 3) +
  #geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
  theme_bw()

3

tsne_out <-
  Rtsne(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
        perplexity = ncol(target_Data)*.15)
pData(target_Data)[, c("tSNE1", "tSNE2")] <- tsne_out$Y[, c(1,2)]
ggplot(pData(target_Data),
       aes(x = tSNE1, y = tSNE2, color = ANN3, shape = ANN2)) +
  geom_point(size = 3) +
  #geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
  theme_bw()

6.2 Clustering high CV Genes

Another approach to explore the data is to calculate the coefficient of variation (\(CV\)) for each gene (\(g\)) using the formula \(CV_g=SD_g/mean_g\). We then identify genes with high CVs that should have large differences across the various profiled segments. This unbiased approach can reveal highly variable genes across the study.

We plot the results using unsupervised hierarchical clustering, displayed as a heatmap.

# create a log2 transform of the data for analysis
assayDataElement(object = target_Data, elt = "log_q") <-
  assayDataApply(target_Data, 2, FUN = log, base = 2, elt = "q_norm")

# create CV function
calc_CV <- function(x) {sd(x) / mean(x)}
CV_dat <- assayDataApply(target_Data,
                         elt = "log_q", MARGIN = 1, calc_CV)
# show the highest CD genes and their CV values
sort(CV_dat, decreasing = TRUE)[1:5]
##   CAMK2N1    AKR1C1      AQP2       REN     GDF15 
## 0.6344192 0.5238995 0.4752667 0.4414027 0.4276390

Table of CV values

# show the highest CD genes and their CV values
datatable(as.data.frame(CV_dat),
          extensions = 'Buttons', options = list (
            order = list(1, 'desc'),
            dom = 'Bftrip',
            buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
          ), caption = "CV values of genes" 
) %>% formatRound(columns=c("CV_dat"), digits=3)

Heatmap genes in the top 3rd of the CV values

GOI <- names(CV_dat)[CV_dat > quantile(CV_dat, 0.75)]

pheatmap(assayDataElement(target_Data[GOI, ], elt = "log_q"),
         scale = "row",
         cutree_cols = 3,
         cutree_rows = 3,
         show_rownames = FALSE, show_colnames = TRUE,
         border_color = NA,
         drop_levels = TRUE,
         clustering_method = "average",
         clustering_distance_rows = "correlation",
         clustering_distance_cols = "correlation",
         breaks = seq(-3, 3, 0.05),
         color = colorRampPalette(c("purple3", "black", "yellow2"))(120),
         annotation_colors = color_list,
         annotation_col = pData(target_Data)[, ann_names])

assayDataElement(object = target_Data, elt = "log_q") <-  assayDataApply(target_Data, 2, FUN = log, base = 2, elt = "q_norm")
log_q <-as.data.frame(assayDataElement(target_Data, elt= "log_q"))

6.2.0 Create subset of data

# determine AOIs to use
#active_aois<-rownames(ann)[ann$patient=="p4"]
active_aois<-rownames(ann)

6.2.1 Clustering high CV genes for subset

Calculating CV values

# create a log2 transform of the data for analysis
assayDataElement(object = target_Data, elt = "log_q") <-
  assayDataApply(target_Data, 2, FUN = log, base = 2, elt = "q_norm")

# create CV function
calc_CV <- function(x) {sd(x) / mean(x)}
CV_dat <- assayDataApply(target_Data[,active_aois],
                         elt = "log_q", MARGIN = 1, calc_CV)

Table of CV values

# show the highest CD genes and their CV values
datatable(as.data.frame(CV_dat),
          extensions = 'Buttons', options = list (
            order = list(1, 'desc'),
            dom = 'Bftrip',
            buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
          ), caption = "CV values of genes" 
) %>% formatRound(columns=c("CV_dat"), digits=3)

Heatmap on of subset, genes in the top 3rd of the CV values

# Identify genes in the top 3rd of the CV values
GOI <- names(CV_dat)[CV_dat > quantile(CV_dat, 0.75)]
pheatmap(assayDataElement(target_Data[GOI,active_aois ], elt = "log_q"),
        scale = "row",
        fontsize_row = 5,
        cutree_cols = 3,
        cutree_rows = 3,
        show_rownames = FALSE, show_colnames = TRUE,
        border_color = NA,
        clustering_method = "average",
        clustering_distance_rows = "correlation",
        clustering_distance_cols = "correlation",
        breaks = seq(-3, 3, 0.05),
        color = colorRampPalette(c("purple3", "black", "yellow2"))(120),
       annotation_colors = color_list,
        annotation_col =
          pData(target_Data)[, ann_names])

7.1 Differential Expression

t-test

gc()
##            used  (Mb) gc trigger   (Mb)  max used   (Mb)
## Ncells 10444514 557.8  136699932 7300.6 139644550 7457.9
## Vcells 78815981 601.4  278361925 2123.8 360660679 2751.7
plots<-list()
tables<-list()
labels<-list()
test<-"ttest"
mtc<-"BY"
#options: "holm"       "hochberg"   "hommel"     "bonferroni" "BH"         "BY"         "fdr" 
counter=1

comps_df<-data.frame(comp='',val='')
for (class in c("DKD","normal")) {
for (active_group1 in unique(ann$region)) {
    for (active_group2 in unique(ann$region)) {
     
      #supress reduncant compares
      if(active_group1==active_group2) {next}
      comp<-paste(sort(c(class, active_group1,active_group2)),collapse = "_")
      if(comp %in% comps_df$comp) {next}
      temp_df<-data.frame(comp=comp ,val=1)
      comps_df<-rbind(comps_df,temp_df)
      
      labels[[counter]]<-paste(active_group1," vs ", active_group2)
      group1<-log_q[,rownames(ann)[ann$class == class & ann$region==active_group1]]
      group2<-log_q[,rownames(ann)[ann$class == class & ann$region==active_group2]]
      
      #run t_tests  
      results<-as.data.frame ( apply(log_q, 1, function(x) t.test(x[colnames(group1)],x[colnames(group2)])$p.value) )
      colnames(results)<-"raw_p_value"
      
      #multiple_testing_correction
      adj_p_value<- p.adjust(results$raw_p_value,method=mtc)
      results<-cbind(results,adj_p_value)
      
      #calc_fdr
      FDR<- p.adjust(results$raw_p_value,method="fdr")
      results<-cbind(results,FDR)
      
      #fold_changes
      #as base data is already log transformed, means need to be subtracted to get FC in log space
      fchanges<-as.data.frame(apply(log_q, 1, function(x) (mean(x[colnames(group1)]) - mean(x[colnames(group2)]))))
      colnames(fchanges)<-"FC"
      #paste("FC",active_group1," / ",active_group2)
      results<-cbind(results,fchanges)
      
      #add genenames
      results$Gene<-rownames(results)
      
      #set categories based on P-value & FDR for plotting
      results$Color <- "NS or FC < 0.5"
      results$Color[results$adj_p_value < 0.05] <- "P < 0.05"
      results$Color[results$FDR < 0.05] <- "FDR < 0.05"
      results$Color[results$FDR < 0.001] <- "FDR < 0.001"
      results$Color[abs(results$FC) < 1] <- "NS or FC < 1"
      results$Color <- factor(results$Color,
                              levels = c("NS or FC < 1", "P < 0.05", "FDR < 0.05", "FDR < 0.001"))
      
      #vulcanoplot
      
      # pick top genes for either side of volcano to label
      # order genes for convenience:
      
      results$invert_P <- (-log10(results$adj_p_value)) * sign(results$FC)
      top_g <- c()
      top_g <- c(top_g,
                 results[ind, 'Gene'][
                   order(results[ind, 'invert_P'], decreasing = TRUE)[1:15]],
                 results[ind, 'Gene'][order(results[ind, 'invert_P'], decreasing = FALSE)[1:15]])
      top_g<- unique(top_g)
      results <- results[, -1*ncol(results)] # remove invert_P from matrix
      
      # Graph results
      plots[[counter]]<- ggplot(results,
                                      aes(x = FC, y = -log10(adj_p_value),
                                          color = Color, label = Gene)) +
        geom_vline(xintercept = c(1, -1), lty = "dashed") +
        geom_hline(yintercept = -log10(0.05), lty = "dashed") +
        geom_point() +
        labs(x = paste("Enriched in", active_group2," <- log2(FC) -> Enriched in", active_group1),
             y = "Significance, -log10(P)",
             color = "Significance") +
        scale_color_manual(values = c(`FDR < 0.001` = "dodgerblue",
                                      `FDR < 0.05` = "lightblue",
                                      `P < 0.05` = "orange2",
                                      `NS or FC < 0.5` = "gray"),
                           guide = guide_legend(override.aes = list(size = 4))) +
        scale_y_continuous(expand = expansion(mult = c(0,0.05))) +
        geom_text_repel(data = subset(results, FDR<0.001 & (-1>FC| FC>1)),
                        point.padding = 0.15, color = "black", size=3.5,
                        min.segment.length = .1, box.padding = .2, lwd = 2,
                        max.overlaps = 50) +
        theme_bw(base_size = 20) +
        theme(legend.position = "bottom") +
        ggtitle(paste("class: ", class," - ",test, mtc,"multitest corr"))
      
      #store tables for display later
      tables[[counter]]<-results
      
       counter = counter+1
      #datatable(subset(results, Gene %in% GOI), rownames=FALSE,caption = paste("DE results ", active_group1," vs ", active_group2))
    }
  }
}
grid.arrange(grobs=plots,ncol=2)

#strangly does not appear in html output??
for (c in (2:counter-1)) {
  #Gene %in% GOI
  
  print(datatable( subset(tables[[c]], Color == "FDR < 0.001" ), 
           rownames=FALSE,
           extensions = 'Buttons', options = list (
              dom = 'Bftrip',
              buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
            ),
           caption = paste("DE results ", labels[[1]]),filter='top') %>% formatRound(columns=c("raw_p_value","adj_p_value","FDR","FC"), digits=3))
  cat('\n\n<!-- -->\n\n')
}            

7.2 DE analysis with LMM

A common statistical approach is to use a linear mixed-effect model (LMM). The LMM allows the user to account for the subsampling per tissue; in other words, we adjust for the fact that the multiple regions of interest placed per tissue section are not independent observations, as is the assumption with other traditional statistical tests. The formulation of the LMM model depends on the scientific question being asked.

Overall, there are two flavors of the LMM model when used with GeoMx data: i) with and ii) without random slope.

When comparing features that co-exist in a given tissue section, a random slope is included in the LMM model. When comparing features that are mutually exclusive in a given tissue section the LMM model does not require a random slope.

Mostly, we use patient/sample as Random Intercept when they are combined on slides.

glomerulus - tubule

# convert test variables to factors

pData(target_Data)$testRegion <- 
    factor(pData(target_Data)$region, c("glomerulus", "tubule"))

pData(target_Data)[["slide"]]<-factor(pData(target_Data)[["slide_name"]])
assayDataElement(object = target_Data, elt = "log_q") <-  assayDataApply(target_Data, 2, FUN = log, base = 2, elt = "q_norm")

lmm_results <- c()
mixedOutmc <-
    mixedModelDE(target_Data,
                 elt = "log_q",
                 modelFormula = ~ testRegion + (1 + testRegion | slide),
                 groupVar = "testRegion",
                 nCores = parallel::detectCores(),
                 multiCore = FALSE)

# format results as data.frame
 r_test <- do.call(rbind, mixedOutmc["lsmeans", ])
 tests <- rownames(r_test)
 r_test <- as.data.frame(r_test)
 r_test$Contrast <- tests
 
# use lapply in case you have multiple levels of your test factor to
# correctly associate gene name with it's row in the results table
 r_test$Gene <- 
     unlist(lapply(colnames(mixedOutmc),
                   rep, nrow(mixedOutmc["lsmeans", ][[1]])))

 r_test$FDR <- p.adjust(r_test$`Pr(>|t|)`, method = "fdr")
 r_test <- r_test[, c("Gene", "Contrast", "Estimate", "Pr(>|t|)", "FDR")]
 lmm_results <- rbind(lmm_results, r_test)
#subset(lmm_results, Gene %in% GOI)
datatable(lmm_results, rownames = FALSE,
          extensions = 'Buttons', options = list (
              dom = 'Bftrip',
              buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
            ),
          caption = "DE results for Genes of Interest (>75% CV)",filter='top') %>% formatRound(columns=c("Estimate","Pr(>|t|)","FDR"), digits=3)

DKD - normal

# convert test variables to factors

pData(target_Data)$testClass <- 
    factor(pData(target_Data)$class, c("DKD", "normal"))

pData(target_Data)[["slide"]]<-factor(pData(target_Data)[["slide_name"]])
assayDataElement(object = target_Data, elt = "log_q") <- assayDataApply(target_Data, 2, FUN = log, base = 2, elt = "q_norm")

lmm_results_d <- c()
mixedOutmc <-
    mixedModelDE(target_Data,
                 elt = "log_q",
                 modelFormula = ~ testClass + (1 | slide),
                 groupVar = "testClass",
                 nCores = parallel::detectCores(),
                 multiCore = FALSE)

# format results as data.frame
 r_test <- do.call(rbind, mixedOutmc["lsmeans", ])
 tests <- rownames(r_test)
 r_test <- as.data.frame(r_test)
 r_test$Contrast <- tests
 
# use lapply in case you have multiple levels of your test factor to
# correctly associate gene name with it's row in the results table
 r_test$Gene <- 
     unlist(lapply(colnames(mixedOutmc),
                   rep, nrow(mixedOutmc["lsmeans", ][[1]])))

 r_test$FDR <- p.adjust(r_test$`Pr(>|t|)`, method = "fdr")
 r_test <- r_test[, c("Gene", "Contrast", "Estimate", "Pr(>|t|)", "FDR")]
 lmm_results_d <- rbind(lmm_results_d, r_test)
#subset(lmm_results, Gene %in% GOI)
datatable(lmm_results_d, rownames = FALSE,
          extensions = 'Buttons', options = list (
              dom = 'Bftrip',
              buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
            ),
          caption = "DE results for Genes of Interest (>75% CV)",filter='top') %>% formatRound(columns=c("Estimate","Pr(>|t|)","FDR"), digits=3)

7.3 Vulcanoplot of LMM

glomerulus - tubule

# Categorize Results based on P-value & FDR for plotting
fc_threshold = 0.5

lmm_results$Color <- paste("NS or FC < ",fc_threshold)
lmm_results$Color[lmm_results$`Pr(>|t|)` < 0.05] <- "P < 0.05"
lmm_results$Color[lmm_results$FDR < 0.05] <- "FDR < 0.05"
lmm_results$Color[lmm_results$FDR < 0.001] <- "FDR < 0.001"
lmm_results$Color[abs(lmm_results$Estimate) < fc_threshold] <- "NS or FC < 1"
lmm_results$Color <- factor(lmm_results$Color,
                        levels = c("NS or FC < 1", "P < 0.05",
                                   "FDR < 0.05", "FDR < 0.001"))




# pick top genes for either side of volcano to label
# order genes for convenience:
lmm_results$invert_P <- (-log10(lmm_results$`Pr(>|t|)`)) * sign(lmm_results$Estimate)
top_g <- c()

#loop here over tested conditions if applicable
#for(location in c("BOTTOM","TOP")) {
top_g <- c(top_g,
           lmm_results[, 'Gene'][
             order(lmm_results[, 'invert_P'], decreasing = TRUE)[1:30]],
           lmm_results[, 'Gene'][
             order(lmm_results[, 'invert_P'], decreasing = FALSE)[1:30]])

top_g <- unique(top_g)
lmm_results <- lmm_results[, -1*ncol(lmm_results)] # remove invert_P from matrix


# get significant genes with FDR < 0.001 and fold change > 0.5
#lmm_results_slice <- lmm_results_slice[lmm_results_slice$FDR < 0.001,]
#lmm_results_slice <- lmm_results_slice[lmm_results_slice$Estimate < -0.5 | lmm_results_slice$Estimate > 0.5,]


# Graph results
print(ggplot(lmm_results,
             aes(x = Estimate, y = -log10(`Pr(>|t|)`),
                 color = Color, label = Gene)) +
        geom_vline(xintercept = c(fc_threshold, -fc_threshold), lty = "dashed") +
        geom_hline(yintercept = -log10(0.05), lty = "dashed") +
        geom_point() +
        labs(x = paste(lmm_results$Contrast, " log2(FC)"),
             y = "Significance, -log10(P)",
             color = "Significance") +
        scale_color_manual(values = c(`FDR < 0.001` = "dodgerblue",
                                      `FDR < 0.05` = "lightblue",
                                      `P < 0.05` = "orange2",
                                      `NS or FC < 1` = "gray"),
                           guide = guide_legend(override.aes = list(size = 4))) +
        scale_y_continuous(expand = expansion(mult = c(0,0.05))) +
        geom_text_repel(data = subset(lmm_results, FDR < 0.001 & abs(lmm_results$Estimate) > fc_threshold),
                        point.padding = 0.15, color = "black",size=5,
                        min.segment.length = .1, box.padding = .2, lwd = 2,
                        max.overlaps = 50) +
        theme_bw(base_size = 16) +
        theme(legend.position = "bottom"))

        #+facet_wrap(~Subset, scales = "free_y"))

DKD - normal

# Categorize Results based on P-value & FDR for plotting
fc_threshold = 0.5

lmm_results_d$Color <- paste("NS or FC < ",fc_threshold)
lmm_results_d$Color[lmm_results_d$`Pr(>|t|)` < 0.05] <- "P < 0.05"
lmm_results_d$Color[lmm_results_d$FDR < 0.05] <- "FDR < 0.05"
lmm_results_d$Color[lmm_results_d$FDR < 0.001] <- "FDR < 0.001"
lmm_results_d$Color[abs(lmm_results_d$Estimate) < fc_threshold] <- "NS or FC < 1"
lmm_results_d$Color <- factor(lmm_results_d$Color,
                        levels = c("NS or FC < 1", "P < 0.05",
                                   "FDR < 0.05", "FDR < 0.001"))




# pick top genes for either side of volcano to label
# order genes for convenience:
lmm_results_d$invert_P <- (-log10(lmm_results_d$`Pr(>|t|)`)) * sign(lmm_results_d$Estimate)
top_g <- c()

#loop here over tested conditions if applicable
#for(location in c("BOTTOM","TOP")) {
top_g <- c(top_g,
           lmm_results_d[, 'Gene'][
             order(lmm_results_d[, 'invert_P'], decreasing = TRUE)[1:30]],
           lmm_results_d[, 'Gene'][
             order(lmm_results_d[, 'invert_P'], decreasing = FALSE)[1:30]])

top_g <- unique(top_g)
lmm_results_d <- lmm_results_d[, -1*ncol(lmm_results_d)] # remove invert_P from matrix


# get significant genes with FDR < 0.001 and fold change > 0.5
#lmm_results_slice <- lmm_results_slice[lmm_results_slice$FDR < 0.001,]
#lmm_results_slice <- lmm_results_slice[lmm_results_slice$Estimate < -0.5 | lmm_results_slice$Estimate > 0.5,]

# Graph results
print(ggplot(lmm_results_d,
             aes(x = Estimate, y = -log10(`Pr(>|t|)`),
                 color = Color, label = Gene)) +
        geom_vline(xintercept = c(fc_threshold, -fc_threshold), lty = "dashed") +
        geom_hline(yintercept = -log10(0.05), lty = "dashed") +
        geom_point() +
        labs(x = paste(lmm_results_d$Contrast, " log2(FC)"),
             y = "Significance, -log10(P)",
             color = "Significance") +
        scale_color_manual(values = c(`FDR < 0.001` = "dodgerblue",
                                      `FDR < 0.05` = "lightblue",
                                      `P < 0.05` = "orange2",
                                      `NS or FC < 1` = "gray"),
                           guide = guide_legend(override.aes = list(size = 4))) +
        scale_y_continuous(expand = expansion(mult = c(0,0.05))) +
        geom_text_repel(data = subset(lmm_results_d,  FDR < 0.001 & abs(lmm_results_d$Estimate) > fc_threshold),
                        point.padding = 0.15, color = "black",size=5,
                        min.segment.length = .1, box.padding = .2, lwd = 2,
                        max.overlaps = 50) +
        theme_bw(base_size = 16) +
        theme(legend.position = "bottom"))

        #+facet_wrap(~Subset, scales = "free_y"))

7.4 Plotting Genes of Interest

my_gois <-unique(subset(lmm_results, `FDR` < 0.001)$Gene)
tmp_tbl<-subset(lmm_results, Gene %in% my_gois)

if(nrow(tmp_tbl) > 1) { 
  datatable(tmp_tbl,rownames = FALSE,caption = "DE results for Genes of Interest ",filter='top') %>% formatRound(columns=c("Estimate","Pr(>|t|)","FDR"), digits=3)
 
for (my_goi in my_gois) {
# show expression for a single target
  a<-ggplot(pData(target_Data),
       aes(x = ANN1, fill = ANN1,
           y = assayDataElement(target_Data[my_goi, ], elt = "q_norm"))) +
  geom_violin() +
  geom_jitter(width = .2) +
  labs(y = paste(my_goi,"Expression")) +
  scale_y_continuous(trans = "log2") +
  facet_wrap(~ANN3, nrow=1) + theme_bw(base_size = 16) +
  theme_bw()
  a
}
}else{
  print("No significant lMM results to plot")
}

7.5 Heatmap of Significant Genes

In addition to generating individual gene box plots or volcano plots, we can again create a heatmap from our data. This time rather than utilizing CV to select genes, we can use the P-value or FDR values to select genes. Here, we plot all genes with an FDR < 0.001.

my_gois <-unique(subset(lmm_results, `FDR` < 0.001)$Gene)

if(length(my_gois)==0) {
  print("No significant results to show")
 
}else{

pheatmap(log2(assayDataElement(target_Data[my_gois, ], elt = "q_norm")),
         scale = "row",
         show_rownames = TRUE, show_colnames = TRUE,
         border_color = NA,
         clustering_method = "average",
         clustering_distance_rows = "correlation",
         clustering_distance_cols = "correlation",
         cutree_cols = 3, cutree_rows = 2,
         breaks = seq(-3, 3, 0.05),
         color = colorRampPalette(c("purple3", "black", "yellow2"))(120),
         annotation_colors = color_list,
         annotation_col = pData(target_Data)[, ann_names])
}

8 Pathway Analysis

Pathway analysis enables exploration of different aggregate gene sets for our experimental questions. Each individual ROI/AOI segment is scored for every pathway of interest, which we can then use to investigate biological differences. We will perform analogous analyses as those outlined in the Differential Expression and Spatial Deconvolution sections of the report for gene set enrichment.

8.1 Scoring Gene Sets

Pathways and gene sets were defined from the Kegg Brite database. We use an R software package called Gene Set Variation Analysis to score each segment within our study. see https://cran.r-project.org/web/packages/msigdbr/vignettes/msigdbr-intro.html for options on collections. We use the KEGG and REACTOME.

h_gene_sets = rbind(msigdbr(species = "human", subcategory = "CP:KEGG"),
                    msigdbr(species = "human", subcategory = "CP:REACTOME"))
#msigdbr(species = "human", subcategory = "CP:BIOCARTA")

msigdbr_list = split(x = h_gene_sets$gene_symbol, f = h_gene_sets$gs_name)

# prepare df for accurate merging back genes later
pw_gene_df<-data.frame(Pathway = names(msigdbr_list))
pw_gene_df$genes<-msigdbr_list
ssgsea_results <- GSVA::gsva(expr = assayDataElement(target_Data,
                            elt = "log_q"),
                            gset.idx.list = msigdbr_list,
                            method = "zscore",
                            min.sz = 5,
                            max.sz = 500,
                            verbose = FALSE)
geneSetObj <-
  NanoStringGeoMxSet(assayData = ssgsea_results,
                     phenoData = AnnotatedDataFrame(pData(target_Data)),
                     protocolData = protocolData(target_Data),
                     featureType = "GeneSet",
                     check = FALSE)

8.2 Differental analysis of pathways

glomerulus - tubule

# # convert test variables to factors
pData(geneSetObj)[["slide"]]<-factor(pData(geneSetObj)[["slide_name"]])
pData(target_Data)$testRegion<-factor(pData(target_Data)$ANN3, unique(count_mat$ANN3))

lmm_ssgsea_results <- c()

mixedOutmc <-
  mixedModelDE(geneSetObj,
               elt = "exprs",
               modelFormula = ~ testRegion + (1 + testRegion | slide),
               groupVar = "testRegion",
               nCores = parallel::detectCores(),
               multiCore = FALSE)

# format results as data.frame
r_ssgsea_test <- do.call(rbind, mixedOutmc["lsmeans", ])
ssgsea_tests <- rownames(r_ssgsea_test)
r_ssgsea_test <- as.data.frame(r_ssgsea_test)
r_ssgsea_test$Contrast <- ssgsea_tests
#r_ssgsea_test$Genes <- msigdbr_list seems unreliable as gsva omits pathways if genes are not in data..

# use lapply in case you have multiple levels of your test factor to
# correctly associate gene name with it's row in the results table
r_ssgsea_test$Pathway <-
  unlist(lapply(colnames(mixedOutmc),
                rep, nrow(mixedOutmc["lsmeans", ][[1]])))


r_ssgsea_test$FDR <- p.adjust(r_ssgsea_test$`Pr(>|t|)`, method = "fdr")
r_ssgsea_test <- r_ssgsea_test[, c("Pathway", "Contrast", "Estimate",
                                   "Pr(>|t|)", "FDR")]
lmm_ssgsea_results <- rbind(lmm_ssgsea_results, r_ssgsea_test)
lmm_ssgsea_results <- merge(lmm_ssgsea_results, pw_gene_df)

DKD - normal

# # convert test variables to factors
pData(geneSetObj)[["slide"]]<-factor(pData(geneSetObj)[["slide_name"]])
pData(target_Data)$testClass<-factor(pData(target_Data)$ANN1, unique(count_mat$ANN1))

lmm_ssgsea_results_d <- c()

mixedOutmc <-
  mixedModelDE(geneSetObj,
               elt = "exprs",
               modelFormula = ~ testClass + (1 | slide),
               groupVar = "testClass",
               nCores = parallel::detectCores(),
               multiCore = FALSE)

# format results as data.frame
r_ssgsea_test <- do.call(rbind, mixedOutmc["lsmeans", ])
ssgsea_tests <- rownames(r_ssgsea_test)
r_ssgsea_test <- as.data.frame(r_ssgsea_test)
r_ssgsea_test$Contrast <- ssgsea_tests
#r_ssgsea_test$Genes <- msigdbr_list seems unreliable as gsva omits pathways if genes are not in data..

# use lapply in case you have multiple levels of your test factor to
# correctly associate gene name with it's row in the results table
r_ssgsea_test$Pathway <-
  unlist(lapply(colnames(mixedOutmc),
                rep, nrow(mixedOutmc["lsmeans", ][[1]])))


r_ssgsea_test$FDR <- p.adjust(r_ssgsea_test$`Pr(>|t|)`, method = "fdr")
r_ssgsea_test <- r_ssgsea_test[, c("Pathway", "Contrast", "Estimate",
                                   "Pr(>|t|)", "FDR")]
lmm_ssgsea_results_d <- rbind(lmm_ssgsea_results_d, r_ssgsea_test)
lmm_ssgsea_results_d <- merge(lmm_ssgsea_results_d, pw_gene_df)

8.2.1 Table of Differental analysis of pathways

glomerulus - tubule

datatable(subset(lmm_ssgsea_results), rownames = FALSE,
          extensions = 'Buttons', options = list (
             pageLength = 10,
              dom = 'Bftrip',
              buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
            ),
          caption = "DE results for Pathways",filter='top') %>% formatRound(columns=c("Estimate","Pr(>|t|)","FDR"), digits=5)

DKD - normal

datatable(subset(lmm_ssgsea_results_d), rownames = FALSE,
          extensions = 'Buttons', options = list (
             pageLength = 10,
              dom = 'Bftrip',
              buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
            ),
          caption = "DE results for Pathways",filter='top') %>% formatRound(columns=c("Estimate","Pr(>|t|)","FDR"), digits=5)

8.3 Vulcanoplot of LMM_Pathways

# Categorize Results based on P-value & FDR for plotting
fc_threshold = 0.5

lmm_ssgsea_results$Color <- "NS or FC < 0.3"
lmm_ssgsea_results$Color[lmm_ssgsea_results$`Pr(>|t|)` < 0.05] <- "P < 0.05"
lmm_ssgsea_results$Color[lmm_ssgsea_results$FDR < 0.05] <- "FDR < 0.05"
lmm_ssgsea_results$Color[lmm_ssgsea_results$FDR < 0.001] <- "FDR < 0.001"
lmm_ssgsea_results$Color[abs(lmm_ssgsea_results$Estimate) < fc_threshold] <- "NS or FC < 0.3"
lmm_ssgsea_results$Color <- factor(lmm_ssgsea_results$Color,
                        levels = c("NS or FC < 0.3", "P < 0.05",
                                   "FDR < 0.05", "FDR < 0.001"))

# pick top pw for either side of volcano to label
# order pw for convenience:
lmm_ssgsea_results$invert_P <- (-log10(lmm_ssgsea_results$`Pr(>|t|)`)) * sign(lmm_ssgsea_results$Estimate)
top_ssgsea_g <- c()

#loop here over tested conditions if applicable
top_ssgsea_g <- c(top_ssgsea_g,
           lmm_ssgsea_results[, 'Pathway'][
               order(lmm_ssgsea_results[, 'invert_P'], decreasing = TRUE)[1:20]],
           lmm_ssgsea_results[, 'Pathway'][
               order(lmm_ssgsea_results[, 'invert_P'], decreasing = FALSE)[1:20]])

top_ssgsea_g <- unique(top_ssgsea_g)
lmm_ssgsea_results <- lmm_ssgsea_results[, -1*ncol(lmm_ssgsea_results)] # remove invert_P from matrix

#lmm_ssgsea_results_slice <- lmm_ssgsea_results_slice[lmm_ssgsea_results_slice$FDR < 1,]

# Graph results
print(ggplot(lmm_ssgsea_results,
       aes(x = Estimate, y = -log10(`Pr(>|t|)`),
           color = Color, label = Pathway)) +
    geom_vline(xintercept = c(0.5, -0.5), lty = "dashed") +
    geom_hline(yintercept = -log10(0.05), lty = "dashed") +
    geom_point() +
    labs(x = paste(lmm_results$Contrast, " FC"),
         y = "Significance, -log10(P)",
         color = "Significance") +
    scale_color_manual(values = c(`FDR < 0.001` = "dodgerblue",
                                  `FDR < 0.05` = "lightblue",
                                  `P < 0.05` = "orange2",
                                  `NS or FC < 0.5` = "gray"),
                       guide = guide_legend(override.aes = list(size = 4))) +
    scale_y_continuous(expand = expansion(mult = c(0,0.05))) +
    geom_text_repel(data = subset(lmm_ssgsea_results, Color == "FDR < 0.05" | Color == "FDR < 0.001"),
                   point.padding = 0.15, color = "black",size=5,
                   min.segment.length = .1, box.padding = .2, lwd = 2,
                   max.overlaps = 50) +
    theme_bw(base_size = 16) +
    theme(legend.position = "bottom"))

#    +facet_wrap(~Subset, scales = "free_y"))

8.4 heatmap of pathways

  active_pw<-filter(lmm_ssgsea_results, `Pr(>|t|)` < 0.001)$Pathway
  active_pw<-filter(lmm_ssgsea_results, FDR < 0.001 )$Pathway
  #active_pw<-filter(lmm_ssgsea_results, Color == "FDR < 0.001")$Pathway
  
  
  active_pw<-top_ssgsea_g
  
  if (length(active_pw)>1) {
  
    print("go")
    
  pw_matrix<-assayDataElement(geneSetObj, elt = "exprs")

  pheatmap(pw_matrix[active_pw,],
         scale = "row",
         show_rownames = TRUE, show_colnames = TRUE,
         fontsize_row = 10,
         border_color = NA,
         clustering_method = "average",
         #clustering_distance_rows = "correlation",
         clustering_distance_cols = "euclidean",
         cutree_cols = 2, cutree_rows = 2,
         breaks = seq(-3, 3, 0.05),
         #color = colorRampPalette(c("purple3", "black", "yellow2"))(120),
         main = "Heatmap of selected Pathways",
         annotation_colors = color_list,
         annotation_col = pData(target_Data)[, ann_names])
  
  }else{
    print("No significant results to display")
  }
## [1] "go"

9 Spatial Deconvolution

9.1 Calculate backgrounds

bg = derive_GeoMx_background(
  norm = assayDataElement(target_Data , elt = "q_norm"),
  probepool = fData(target_Data)$Module,
  negnames = c("NegProbe-CTP01","NegProbe-Kilo","Negative Probe", "NegProbe-WTX" ))
  #negnames = "NegProbe-WTX")

9.2 Load cell profile

A “cell profile matrix” is a pre-defined matrix that specifies the expected expression profiles of each cell type in the experiment. The SpatialDecon library comes with one such matrix pre-loaded, the “SafeTME” matrix, designed for estimation of immune and stroma cells in the tumor microenvironment. (This matrix was designed to avoid genes commonly expressed by cancer cells; see the SpatialDecon manuscript for details.). Otherwise, load specific profiles from https://github.com/Nanostring-Biostats/CellProfileLibrary/tree/NewProfileMatrices

#safeTME
data("safeTME")
data("safeTME.matches")
current_cell_profile<-safeTME

#see: https://github.com/Nanostring-Biostats/CellProfileLibrary/tree/NewProfileMatrices

# current_cell_profile <- download_profile_matrix(species = "Human",
#                                        age_group = "Adult",
#                                        matrixname = "Bladder_MCA")

heatmap(sweep(current_cell_profile, 1, apply(current_cell_profile, 1, max), "/"),
        labRow = NA, margins = c(10, 5), cexCol = 0.7)

9.3 Run spatial deconvolution

# vector identifying pure tumor segments:
#target_Data$istumor = target_Data$ANN3 == "CORE" & target_Data$ANN1 == "PanCK+"

res = runspatialdecon(object = target_Data,
                      norm_elt = "q_norm",
                      raw_elt = "exprs",
                      #is_pure_tumor = target_Data$istumor,
                      cell_counts = target_Data$nuclei,
                      X = current_cell_profile,
                      cellmerges = safeTME.matches,              # safeTME.matches object, used by default
                      #n_tumor_clusters = 5,                      # how many distinct tumor profiles to append to safeTME
                      align_genes = TRUE)

9.3.1 Spatial deconvolution heatmaps

Abundance

# NOTE: check clustering.. why different?

#set display thresholds
thresh <- signif(quantile(res$beta, 0.97), 2)

# plot stored to keep clustering for later
p1<-pheatmap(pmin(t(res$beta),thresh),
         #scale = "row", 
         cutree_cols = 3,
         cutree_rows = 2,
         fontsize_row = 12,
         show_rownames = TRUE, show_colnames = TRUE,
         angle_col = "90",
         border_color = NA,
         #clustering_method = "average",
         #clustering_distance_rows = "correlation",
         #clustering_distance_cols = "correlation",
         legend_breaks = c(round(seq(0, thresh, length.out = 5))[-5], thresh),
         legend_labels = c(round(seq(0, thresh, length.out = 5))[-5], paste0("Abundance scores,\ntruncated above at ", thresh)),
         #breaks = seq(0, 5, 1),
         color = colorRampPalette(c("white","darkblue"))(100),
         annotation_colors = color_list,
         annotation_col = pData(target_Data)[, ann_names]
         )

#p1

Proportional

# proportions:
props <- replace(res$prop_of_nontumor, is.na(res$prop_of_nontumor), 0)

p2<-pheatmap(t(props),
         #scale = "row", 
         cutree_cols = 3,
         cutree_rows = 2,
         fontsize_row = 12,
         show_rownames = TRUE, show_colnames = TRUE,
         angle_col = "90",
         border_color = NA,
         #clustering_method = "average",
         #clustering_distance_rows = "correlation",
         #clustering_distance_cols = "correlation",
         legend_breaks = round(seq(0, max(props) * 0.99, length.out = 5), 2),
         legend_labels = c(round(seq(0, max(props), length.out = 5), 2)[-5], "Proportion of all\nfitted populations"),
         color = colorRampPalette(c("white","darkblue"))(100),
         annotation_colors = color_list,
         annotation_col = pData(target_Data)[, ann_names])

#p2

Scaled

# scaled abundances:
epsilon <- min(res$beta[res$beta > 0])
mat <- sweep(res$beta, 1, pmax(apply(res$beta, 1, max), epsilon), "/")

pheatmap(t(mat),
         #scale = "row",
         cutree_cols = 3,
         cutree_rows = 3,
         fontsize_row = 12,
         show_rownames = TRUE, show_colnames = TRUE,
         angle_col = "90",
         border_color = NA,
         #clustering_method = "average",
         #clustering_distance_rows = "correlation",
         #clustering_distance_cols = "correlation",
         legend_breaks = c(round(seq(0, 1, length.out = 5), 2)[-5], 1),
         legend_labels = c(round(seq(0, 1, length.out = 5), 2)[-5], "Scaled abundance\n(ratio to max)"),
         color = colorRampPalette(c("white","darkblue"))(100),
         annotation_colors = color_list,
         annotation_col = pData(target_Data)[, ann_names])

9.4 Barplots

abundance

# define variables to show in heatmaps:
variables_to_plot <- c("slide_name", "region", "class")

col <- cellcols

layout(matrix(c(1, 2, 3, 3), nrow = 2),
       widths = c(10, 3, 10, 3),
       heights = c(1, 8, 10),
      )

par(mar = c(0, 8.2, 0, 0.2))
plot(p1$tree_col, labels = F, main = "", ylab = "", yaxt = "n")
par(mar = c(15, 8, 0, 0))

# data to plot:
mat <- t(res$beta)[, p1$tree_col$order]
# infer scale of negative y-axis for annotation colorbars
ymin <- -max(colSums(mat)) * 0.15
if (!is.finite(ymin)) {
  ymin <- 0
}

# draw barplot:
bp <- barplot(mat,
              cex.lab = 1.5,
              col = col, border = NA,
              cex.names = 1.1,
              las = 2, main = "", ylab = "Abundance scores",
              ylim = c(ymin, max(colSums(mat)))
)


# add color bars for annotations
for (name in rev(variables_to_plot)) {
  yrange <- seq(ymin / 3, ymin, length.out = length(variables_to_plot) + 1)[match(name, variables_to_plot) + c(0, 1)]
  xwidth <- (bp[2] - bp[1]) / 2
  for (i in 1:ncol(mat)) {
    rect(bp[i] - xwidth, yrange[2], bp[i] + xwidth, yrange[1],
         # border = NA, col = ann_colors[[name]][segmentAnnotations[match(colnames(mat)[i], segmentAnnotations$segmentID), name]]
         #border = NA, col = ann_colors[[name]][ann[p1$tree_col$order[i], name]]
         border = NA, col = color_list[[name]][ann[colnames(mat)[i], name]]
    )
  }
}
axis(2,
     at = seq(ymin / 3, ymin, length.out = length(variables_to_plot) + 2)[-c(1, length(variables_to_plot) + 2)],
     las = 2, labels = variables_to_plot, lty = 0, cex.axis = 1.2
)

#draw a legend:
par(mar = c(0.1, 0.1, 0.1, 0.1))
frame()
legendcols <- legendnames <- c()
#for (name in rev(names(ann_colors))) {
for (name in c("slide_name", "region","class")) {
  legendcols <- c(legendcols, NA, color_list[[name]], NA)
  legendnames <- c(legendnames, name, names(color_list[[name]]), NA)
}
legend("center",
       pch = 15,
       cex = 1.5,
       col = c(legendcols, rep(NA, 1), rev(col)),
       legend = c(legendnames, "Cell type", rev(names(col))),
)

proportional

# define variables to show in heatmaps:
variables_to_plot <- c("slide name", "segment","pheno")

layout(matrix(c(1, 2, 3, 3), nrow = 2),
       widths = c(10, 3, 10, 3),
       heights = c(1, 8, 10),
      )

par(mar = c(0, 8.2, 0, 0.2))
plot(p2$tree_col, labels = F, main = "", ylab = "", yaxt = "n")
par(mar = c(15, 8, 0, 0))

# data to plot:
mat <- t(res$prop_of_nontumor)[, p2$tree_col$order]
  mat <- replace(mat, is.na(mat), 0)
  # infer scale of negative y-axis for annotation colorbars
  ymin <- -0.15

# draw barplot:
bp <- barplot(mat,
              cex.lab = 1.5,
              col = col, border = NA,
              cex.names = 1.1,
              las = 2, main = "", ylab = "Proportion of fitted cells",
              ylim = c(ymin, max(colSums(mat)))
)

# add color bars for annotations
for (name in rev(variables_to_plot)) {
  yrange <- seq(ymin / 3, ymin, length.out = length(variables_to_plot) + 1)[match(name, variables_to_plot) + c(0, 1)]
  xwidth <- (bp[2] - bp[1]) / 2
  for (i in 1:ncol(mat)) {
    rect(bp[i] - xwidth, yrange[2], bp[i] + xwidth, yrange[1],
         # border = NA, col = ann_colors[[name]][segmentAnnotations[match(colnames(mat)[i], segmentAnnotations$segmentID), name]]
         #border = NA, col = ann_colors[[name]][ann[p2$tree_col$order[i], name]]
         border = NA, col = color_list[[name]][ann[colnames(mat)[i], name]]
    )
  }
}
axis(2,
     at = seq(ymin / 3, ymin, length.out = length(variables_to_plot) + 2)[-c(1, length(variables_to_plot) + 2)],
     las = 2, labels = variables_to_plot, lty = 0, cex.axis = 1.2
)


#draw a legend:
par(mar = c(0.1, 0.1, 0.1, 0.1))
frame()
legendcols <- legendnames <- c()
#for (name in rev(names(ann_colors))) {
for (name in c("slide name", "segment","pheno")) {
  legendcols <- c(legendcols, NA, color_list[[name]], NA)
  legendnames <- c(legendnames, name, names(color_list[[name]]), NA)
}
legend("center",
       pch = 15,
       cex = 1.4,
       col = c(legendcols, rep(NA, 1), rev(col)),
       legend = c(legendnames, "Cell type", rev(names(col))),
)

10 Code & Versions

Pipelineversion: v1 based on: https://bioconductor.org/packages/devel/workflows/vignettes/GeoMxWorkflows/inst/doc/GeomxTools_RNA-NGS_Analysis.html

The underlying code can be downloaded from the ‘Code’, button on the top of this page. Choose option ‘download Rmd’ to download the full pipeline which can be opened in R or Rstudio. Some filepaths are hardcoded and need to be changed according to your setup.

10.1 R session information

sessionInfo()
## R version 4.2.1 (2022-06-23 ucrt)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 19043)
## 
## Matrix products: default
## 
## locale:
## [1] LC_COLLATE=English_Netherlands.utf8  LC_CTYPE=English_Netherlands.utf8   
## [3] LC_MONETARY=English_Netherlands.utf8 LC_NUMERIC=C                        
## [5] LC_TIME=English_Netherlands.utf8    
## 
## attached base packages:
## [1] stats4    stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
##  [1] gage_2.46.1             clusterProfiler_4.4.4   limma_3.52.2           
##  [4] RColorBrewer_1.1-3      gridExtra_2.3           plotly_4.10.0          
##  [7] DT_0.25                 ggrepel_0.9.1           pheatmap_1.0.12        
## [10] Rtsne_0.16              umap_0.2.9.0            cowplot_1.1.1          
## [13] reshape2_1.4.4          scales_1.2.1            ggforce_0.3.4          
## [16] dplyr_1.0.10            knitr_1.40              msigdbr_7.5.1          
## [19] GSVA_1.44.2             SpatialDecon_1.6.0      GeoMxWorkflows_1.2.0   
## [22] GeomxTools_3.1.1        NanoStringNCTools_1.5.0 ggplot2_3.3.6          
## [25] S4Vectors_0.34.0        Biobase_2.56.0          BiocGenerics_0.42.0    
## 
## loaded via a namespace (and not attached):
##   [1] utf8_1.2.2                  reticulate_1.26            
##   [3] R.utils_2.12.0              tidyselect_1.1.2           
##   [5] lme4_1.1-30                 RSQLite_2.2.16             
##   [7] AnnotationDbi_1.58.0        htmlwidgets_1.5.4          
##   [9] grid_4.2.1                  BiocParallel_1.30.3        
##  [11] scatterpie_0.1.8            munsell_0.5.0              
##  [13] ScaledMatrix_1.4.1          codetools_0.2-18           
##  [15] future_1.28.0               withr_2.5.0                
##  [17] colorspace_2.0-3            GOSemSim_2.22.0            
##  [19] progressr_0.11.0            highr_0.9                  
##  [21] uuid_1.1-0                  rstudioapi_0.14            
##  [23] SingleCellExperiment_1.18.0 DOSE_3.22.1                
##  [25] listenv_0.8.0               labeling_0.4.2             
##  [27] MatrixGenerics_1.8.1        GenomeInfoDbData_1.2.8     
##  [29] polyclip_1.10-0             bit64_4.0.5                
##  [31] farver_2.1.1                rhdf5_2.40.0               
##  [33] downloader_0.4              treeio_1.20.2              
##  [35] repmis_0.5                  parallelly_1.32.1          
##  [37] vctrs_0.4.1                 generics_0.1.3             
##  [39] xfun_0.32                   ggthemes_4.2.4             
##  [41] R6_2.5.1                    GenomeInfoDb_1.32.4        
##  [43] graphlayouts_0.8.1          ggbeeswarm_0.6.0           
##  [45] rsvd_1.0.5                  ggiraph_0.8.3              
##  [47] gridGraphics_0.5-1          bitops_1.0-7               
##  [49] rhdf5filters_1.8.0          cachem_1.0.6               
##  [51] reshape_0.8.9               fgsea_1.22.0               
##  [53] DelayedArray_0.22.0         assertthat_0.2.1           
##  [55] ggraph_2.0.6                enrichplot_1.16.2          
##  [57] rgeos_0.5-9                 beeswarm_0.4.0             
##  [59] gtable_0.3.1                beachmat_2.12.0            
##  [61] globals_0.16.1              tidygraph_1.2.2            
##  [63] rlang_1.0.5                 systemfonts_1.0.4          
##  [65] splines_4.2.1               lazyeval_0.2.2             
##  [67] BiocManager_1.30.18         yaml_2.3.5                 
##  [69] crosstalk_1.2.0             qvalue_2.28.0              
##  [71] tools_4.2.1                 ggplotify_0.1.0            
##  [73] ellipsis_0.3.2              jquerylib_0.1.4            
##  [75] Rcpp_1.0.9                  plyr_1.8.7                 
##  [77] sparseMatrixStats_1.8.0     zlibbioc_1.42.0            
##  [79] purrr_0.3.4                 RCurl_1.98-1.8             
##  [81] openssl_2.0.3               viridis_0.6.2              
##  [83] SeuratObject_4.1.1          SummarizedExperiment_1.26.1
##  [85] magrittr_2.0.3              data.table_1.14.2          
##  [87] RSpectra_0.16-1             DO.db_2.9                  
##  [89] lmerTest_3.1-3              R.cache_0.16.0             
##  [91] matrixStats_0.62.0          patchwork_1.1.2            
##  [93] evaluate_0.16               xtable_1.8-4               
##  [95] XML_3.99-0.10               readxl_1.4.1               
##  [97] IRanges_2.30.1              compiler_4.2.1             
##  [99] tibble_3.1.8                shadowtext_0.1.2           
## [101] crayon_1.5.1                minqa_1.2.4                
## [103] R.oo_1.25.0                 htmltools_0.5.3            
## [105] ggfun_0.0.7                 aplot_0.1.7                
## [107] tidyr_1.2.1                 DBI_1.1.3                  
## [109] tweenr_2.0.2                EnvStats_2.7.0             
## [111] MASS_7.3-57                 boot_1.3-28                
## [113] BiocStyle_2.24.0            babelgene_22.3             
## [115] Matrix_1.4-1                cli_3.3.0                  
## [117] R.methodsS3_1.8.2           igraph_1.3.4               
## [119] parallel_4.2.1              GenomicRanges_1.48.0       
## [121] pkgconfig_2.0.3             numDeriv_2016.8-1.1        
## [123] sp_1.5-0                    ggtree_3.4.2               
## [125] annotate_1.74.0             vipor_0.4.5                
## [127] bslib_0.4.0                 XVector_0.36.0             
## [129] yulab.utils_0.0.5           stringr_1.4.1              
## [131] digest_0.6.29               graph_1.74.0               
## [133] Biostrings_2.64.1           rmarkdown_2.16             
## [135] cellranger_1.1.0            fastmatch_1.1-3            
## [137] tidytree_0.4.0              DelayedMatrixStats_1.18.0  
## [139] GSEABase_1.58.0             rjson_0.2.21               
## [141] nloptr_2.0.3                lifecycle_1.0.2            
## [143] nlme_3.1-157                outliers_0.15              
## [145] jsonlite_1.8.0              Rhdf5lib_1.18.2            
## [147] viridisLite_0.4.1           askpass_1.1                
## [149] fansi_1.0.3                 pillar_1.8.1               
## [151] lattice_0.20-45             GGally_2.1.2               
## [153] KEGGREST_1.36.3             fastmap_1.1.0              
## [155] httr_1.4.4                  GO.db_3.15.0               
## [157] glue_1.6.2                  png_0.1-7                  
## [159] bit_4.0.4                   stringi_1.7.8              
## [161] sass_0.4.2                  HDF5Array_1.24.2           
## [163] blob_1.2.3                  BiocSingular_1.12.0        
## [165] memoise_2.0.1               ape_5.6-2                  
## [167] irlba_2.3.5                 future.apply_1.9.1

10.2 References

knitr::knit_exit()
LS0tDQp0aXRsZTogIk5hbm9zdHJpbmcgR2VvTXggYW5hbHlzaXMiDQphdXRob3I6ICJJZXMgTmlqbWFuIg0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBmYWxzZQ0KICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMw0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFKQ0KYGBgDQoNCiFbXShDOi9Vc2Vycy9wa2xvb3N0ZXJtYW4vRG9jdW1lbnRzL2dlbmVyYWxfd29ya2Zsb3cvVVNFUS1sb2dvLXN1YnRpdGxlLmpwZykNCg0KIyBLbGFudDogVXNlcQ0KDQojIFByb2plY3Q6IE9yZ2FuIGF0bGFzDQoNCiMgRGF0YXNldDogS2lkbmV5IGRhdGENCg0KKipkYXRlOiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVIICVNICVhICVkICVCLCAlWScpYCoqDQoNCiFbXShDOi9Vc2Vycy9wa2xvb3N0ZXJtYW4vRG9jdW1lbnRzL2dlbmVyYWxfd29ya2Zsb3cvZGVjb3JhdGlvbi1zdHJva2UtZmxhdC12Mi5wbmcpDQoNCioqbG9hZGluZyBkZXBlbmRlbmNpZXMqKiBQbGVhc2UgbWFrZSBzdXJlIHRoZSBmb2xsb3dpbmcgcGFja2FnZXMgYXJlDQppbnN0YWxsZWQgYW5kIHJlcXVpcmVkIGxpYnJhcmllcyBjYW4gYmUgbG9hZGVkOiBcKg0KaW5zdGFsbC5wYWNrYWdlcygicGtnYnVpbGQiKSAvLyBwa2didWlsZDo6Y2hlY2tfYnVpbGRfdG9vbHMoKSBcKg0KaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKSBcKg0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJOYW5vc3RyaW5nLUJpb3N0YXRzL05hbm9TdHJpbmdOQ1Rvb2xzIikgXCoNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiTmFub3N0cmluZy1CaW9zdGF0cy9HZW9teFRvb2xzIiwgcmVmID0gImRldiIpDQpcKiBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoIk5hbm9zdHJpbmctQmlvc3RhdHMvR2VvTXhXb3JrZmxvd3MiLCByZWYgPQ0KIm1haW4iKSBcKiBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiR2VvTXhXb3JrZmxvd3MiKSBcKg0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJEYXZpc0xhYm9yYXRvcnkvc3RhbmRSIikgXCoNCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJTcGF0aWFsRGVjb24iKSBcKiBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiR1NWQSIpIFwqDQppbnN0YWxsLnBhY2thZ2VzKCJwbG90bHkiKSBcKiBpbnN0YWxsLnBhY2thZ2VzKCJEVCIpIFwqDQppbnN0YWxsLnBhY2thZ2VzKCJtc2lnZGJyIikgI2luc3RhbGwucGFja2FnZXMoImRpZ2VzdCIpDQojaW5zdGFsbC5wYWNrYWdlcygicm1hcmtkb3duIikgI2luc3RhbGwucGFja2FnZXMoImthYmxlIikNCg0KYGBge3IgbG9hZF9saWJyYXJpZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCiNsb2FkIGxpYnJhcmllcw0KbGlicmFyeShOYW5vU3RyaW5nTkNUb29scykNCmxpYnJhcnkoR2VvbXhUb29scykNCmxpYnJhcnkoR2VvTXhXb3JrZmxvd3MpDQpsaWJyYXJ5KFNwYXRpYWxEZWNvbikNCmxpYnJhcnkoR1NWQSkgI2ZvciBwYXRod2F5IGFuYWx5c2VzDQpsaWJyYXJ5KG1zaWdkYnIpICNmb3IgbW9sZWN1bGFyIHNpZ25hdHVyZXMgaW4gcGF0aHdheSBhbmFseXNlcw0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdnZm9yY2UpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHNjYWxlcykgIyBmb3IgcGVyY2VudA0KbGlicmFyeShyZXNoYXBlMikgICMgZm9yIG1lbHQNCmxpYnJhcnkoY293cGxvdCkgICAjIGZvciBwbG90X2dyaWQNCmxpYnJhcnkodW1hcCkNCmxpYnJhcnkoUnRzbmUpDQpsaWJyYXJ5KHBoZWF0bWFwKSAgIyBmb3IgcGhlYXRtYXANCmxpYnJhcnkoZ2dyZXBlbCkgDQpsaWJyYXJ5KHNjYWxlcykgI2ZvciBnZ3Bsb3QgcGVhdWRvbG9nIHRvIHByZXZlbnQgZXJyb3JzIG9uIGxvZygwKQ0KbGlicmFyeShEVCkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShncmlkRXh0cmEpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCg0KbGlicmFyeShsaW1tYSkNCmxpYnJhcnkoY2x1c3RlclByb2ZpbGVyKSAjcGF0aHdheSBhbmFseXNpcw0KbGlicmFyeShnYWdlKQ0KYGBgDQoNCiMgMSBsb2FkaW5nIGJhc2UgZmlsZXMNCg0KYGBge3IgbG9hZGluZ19iYXNlX2RhdGF9DQojIFJlZmVyZW5jZSB0aGUgbWFpbiBmb2xkZXIgJ2ZpbGUucGF0aCcgY29udGFpbmluZyB0aGUgc3ViLWZvbGRlcnMgd2l0aCBlYWNoIGRhdGEgZmlsZSB0eXBlOg0KDQpkYXRhZGlyPC1maWxlLnBhdGgoIkM6L1VzZXJzL3BrbG9vc3Rlcm1hbi9Eb2N1bWVudHMvZ2VuZXJhbF93b3JrZmxvdy9LaWRuZXlfRGF0YXNldCIpDQojZGF0YWRpcjwtZmlsZS5wYXRoKCJDOi9Vc2Vycy9waW1rbC9PbmVEcml2ZS9Eb2N1bWVudGVuL1VNQ1UvUi9nZW5lcmFsX3dvcmtmbG93L0tpZG5leV9EYXRhc2V0LyIpDQpgYGANCg0KVG8gbG9jYXRlIGEgc3BlY2lmaWMgZmlsZSBwYXRoIHJlcGxhY2UgdGhlIGFib3ZlIGxpbmUgd2l0aCBkYXRhZGlyIFw8LQ0KZmlsZS5wYXRoKCJcfi9Gb2xkZXIvU3ViRm9sZGVyL0RhdGFMb2NhdGlvbiIpIHJlcGxhY2UgdGhlIEZvbGRlciwNClN1YkZvbGRlciwgRGF0YUxvY2F0aW9uIGFzIG5lZWRlZC4gVGhlIERhdGFMb2NhdGlvbiBmb2xkZXIgc2hvdWxkDQpjb250YWluIGEgZGNjcywgcGtjcywgYW5kIGFubm90YXRpb24gZm9sZGVyIHdpdGggZWFjaCBzZXQgb2YgZmlsZXMNCnByZXNlbnQgYXMgbmVlZGVkIGF1dG9tYXRpY2FsbHkgbGlzdCBmaWxlcyBpbiBlYWNoIGRpcmVjdG9yeSBmb3IgdXNlLg0KDQoqKlRha2UgY2FyZSB5b3UgaW1wb3J0IGEgY29sdW1uIHdpdGggbnVjbGVpIGNvdW50IHNlcGFyYXRlbHkgaWYgeW91DQp3YW50LioqDQoNCmBgYHtyIHBhcnNlX2ZpbGVzfQ0KRENDRmlsZXMgPC0gZGlyKGZpbGUucGF0aChkYXRhZGlyLCAiZGNjcyIpLCBwYXR0ZXJuID0gIi5kY2MkIiwNCiAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVFJVRSwgcmVjdXJzaXZlID0gVFJVRSkNClBLQ0ZpbGVzIDwtIGRpcihmaWxlLnBhdGgoZGF0YWRpciwgInBrY3MiKSwgcGF0dGVybiA9ICIucGtjJCIsDQogICAgICAgICAgICAgICAgZnVsbC5uYW1lcyA9IFRSVUUsIHJlY3Vyc2l2ZSA9IFRSVUUpDQpTYW1wbGVBbm5vdGF0aW9uRmlsZSA8LQ0KICBkaXIoZmlsZS5wYXRoKGRhdGFkaXIsICJhbm5vdGF0aW9uIiksIHBhdHRlcm4gPSAiXltefl0iLA0KICAgICAgZnVsbC5uYW1lcyA9IFRSVUUsIHJlY3Vyc2l2ZSA9IFRSVUUpDQpgYGANCg0KIyAyIGxvYWQgZGF0YQ0KDQpgYGB7ciBsb2FkX2RhdGF9DQpEYXRhIDwtDQogIHJlYWROYW5vU3RyaW5nR2VvTXhTZXQoZGNjRmlsZXMgPSBEQ0NGaWxlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICBwa2NGaWxlcyA9IFBLQ0ZpbGVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHBoZW5vRGF0YUZpbGUgPSBTYW1wbGVBbm5vdGF0aW9uRmlsZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBwaGVub0RhdGFTaGVldCA9ICJUZW1wbGF0ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgcGhlbm9EYXRhRGNjQ29sTmFtZSA9ICJTYW1wbGVfSUQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHByb3RvY29sRGF0YUNvbE5hbWVzID0gYygiYW9pIiwgInJvaSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGV4cGVyaW1lbnREYXRhQ29sTmFtZXMgPSBjKCJwYW5lbCIpKQ0KDQojc2F2ZSBkYXRhIHRvIHByZXZlbnQgbG9hZGluZyB0aW1lIGZvciByZXRha2VzDQpzYXZlRGF0YTwtRGF0YQ0KI0RhdGE8LXNhdmVEYXRhDQoNCiNjaGFuZ2UgRGF0YSBjb2x1bW4gbmFtZXMgYW5kIG1hbnVhbCBjb3JyZWN0aW9uIG9mIHNwZWxsaW5nIGVycm9ycw0KRGF0YUBwaGVub0RhdGFAZGF0YVtbInNsaWRlX25hbWUiXV08LURhdGFAcGhlbm9EYXRhQGRhdGFbWyJzbGlkZSBuYW1lIl1dDQpEYXRhQHBoZW5vRGF0YUBkYXRhW1sic2xpZGUgbmFtZSJdXTwtICBOVUxMDQpEYXRhQHBoZW5vRGF0YUBkYXRhW1siQU5ONCJdXTwtZ3N1YigiaSIsICIiLCBEYXRhQHBoZW5vRGF0YUBkYXRhW1siQU5ONCJdXSkNCg0KIysxIHJlZmVyZW5jZXMgdGhlIHNsaWRlIG5hbWUgY29sdW1uDQphbm5fc2l6ZTwtbGVuZ3RoKGNvbG5hbWVzKERhdGFAcGhlbm9EYXRhQGRhdGEpW2dyZXBsKCJBTk4iLGNvbG5hbWVzKERhdGFAcGhlbm9EYXRhQGRhdGEpKV0pKzEgDQphbm5fbmFtZXM8LWMoY29sbmFtZXMoRGF0YUBwaGVub0RhdGFAZGF0YSlbZ3JlcGwoIkFOTiIsY29sbmFtZXMoRGF0YUBwaGVub0RhdGFAZGF0YSkpXSwic2xpZGVfbmFtZSIpDQoNCiMgRmVlbCBmcmVlIHRvIGNoYW5nZSB0aGUgb3JkZXIgb2Ygd2hpY2ggY29sb3JzIGFyZSBhcHBvaW50ZWQuDQpjb2xvcjwtYygiI0EzNDlBNCIsICIjRkZGRjMzIiwgIiNFNzI5OEEiLCAiIzA5MTgzMyIsICIjMUI5RTc3IiwgIiNEOTVGMDIiLCAiIzc1NzBCMyIsICAiIzY2QTYxRSIsICIjRTZBQjAyIiwgIiM4REQzQzciLCAiIzlGMDAwRiIsICIjQkVCQURBIiwgIiNGQjgwNzIiLCAiIzgwQjFEMyIsICIjRkRCNDYyIiwgIiNCM0RFNjkiLCAiI0ZDQ0RFNSIsICIjRDlEOUQ5IiwgIiNCQzgwQkQiLCAiI0NDRUJDNSIsICIjRkZFRDZGIiwgIiMzNzdFQjgiLCAiIzk4NEVBMyIsICIjNERBRjRBIiwgIiNGRjcxQ0UiLCAiI0ZGN0YwMCIsICIjQTZDRUUzIiwgIiMxRjc4QjQiLCAiI0IyREY4QSIsICIjMzNBMDJDIiwgIiNGQjlBOTkiLCAiI0UzMUExQyIsICIjRkRCRjZGIiwgIiNDQUIyRDYiLCAiIzZBM0Q5QSIsICIjRkZGRjk5IiwgIiNCMTU5MjgiKQ0Kc2hvd19jb2woY29sb3IpDQoNCiMgVXNlIGNvdW50IGFuZCBjb3VudF9tYXggdG8gc2V0IHRoZSByYW5nZSBvZiBjb2xvciBuZWVkZWQgZm9yIGVhY2ggY29sdW1uLg0KIyBXaXRoIHNldE5hbWVzKCkgdGhlIGNvbG9yIGNvZGUgaXMgbGlua2VkIHRvIGVhY2ggdW5pcXVlIGluZGl2aWR1YWwgdmFsdWUgb2YgZWFjaCBjb2x1bW4uDQpjb3VudD0xDQpjb2xvcl9saXN0ID0gbGlzdCgpDQpmb3IgKGFubiBpbiBhbm5fbmFtZXMpIHsNCiAgY291bnRfbWF4ID0gY291bnQrbGVuZ3RoKHVuaXF1ZShEYXRhQHBoZW5vRGF0YUBkYXRhW1thbm5dXSkpLTENCiAgY29sb3JfbGlzdFtbYW5uXV08LXNldE5hbWVzKGNvbG9yW2NvdW50OmNvdW50X21heF0sIHVuaXF1ZShEYXRhQHBoZW5vRGF0YUBkYXRhW1thbm5dXSkpDQogIGNvdW50PWNvdW50X21heCsxDQp9DQpjb2xvcl9saXN0DQpgYGANCg0KYGBge3J9DQpwYXN0ZSgiUmVhZHMgZnJvbSBmb2xsb3dpbmcgcnVucyB1c2VkOiAiLHVuaXF1ZShwRGF0YShwcm90b2NvbERhdGEoRGF0YSkpJFNlcVNldElkKSkNCg0KYGBgDQoNCiMgMyBTdHVkeSBkZXNpZ24NCg0KYGBge3IgYW5ub3RhdGV9DQpwa2NzIDwtIGFubm90YXRpb24oRGF0YSkNCm1vZHVsZXMgPC0gZ3N1YigiLnBrYyIsICIiLCBwa2NzKQ0Ka2FibGUoZGF0YS5mcmFtZShQS0NzID0gcGtjcywgbW9kdWxlcyA9IG1vZHVsZXMpKQ0KYGBgDQoNClNlbGVjdCB0aGUgYW5ub3RhdGlvbnMgd2Ugd2FudCB0byBzaG93LCB1c2UgXGBcYCB0byBzdXJyb3VuZCBjb2x1bW4NCm5hbWVzIHdpdGggc3BhY2VzIG9yIHNwZWNpYWwgc3ltYm9scw0KDQpgYGB7ciBzZWxlY3RfYW5ub3RhdGlvbnN9DQpjb3VudF9tYXQgPC0gZHBseXI6OmNvdW50KHBEYXRhKERhdGEpLCBBTk4xLEFOTjIsQU5OMyxBTk40LHNsaWRlX25hbWUpDQpgYGANCg0KU2ltcGxpZnkgdGhlIHNsaWRlIG5hbWVzIGlmIHJlcXVpcmVkDQoNCmBgYHtyIHNpbXBsaWZ5X25hbWVzfQ0KY291bnRfbWF0JHNsaWRlX25hbWUgPC0gZ3N1YigiZGlzZWFzZSIsICJkIiwgZ3N1Yigibm9ybWFsIiwgIm4iLCBjb3VudF9tYXQkc2xpZGVfbmFtZSkpDQpjb3VudF9tYXQkQU5OMiA8LSBnc3ViKCJkaXNlYXNlIiwgImQiLCBnc3ViKCJub3JtYWwiLCAibiIsIGNvdW50X21hdCRBTk4yKSkNCiNjb3VudF9tYXQkcGF0aF9hbm4gPC0gZ3N1YigiaSIsICIiLCBjb3VudF9tYXQkcGF0aF9hbm4pICNjb3JyZWN0aW5nIHNwZWxsaW5nIGVycm9yDQpgYGANCg0KR2F0aGVyIHRoZSBkYXRhIGFuZCBwbG90IGluIG9yZGVyOiBjbGFzcywgc2xpZGUgbmFtZSwgcmVnaW9uLCBzZWdtZW50DQoNCmBgYHtyIGdhdGhlcl9kYXRhfQ0KdGVzdF9nciA8LSBnYXRoZXJfc2V0X2RhdGEoY291bnRfbWF0LCAxOmFubl9zaXplKQ0KdGVzdF9nciR4IDwtIGZhY3Rvcih0ZXN0X2dyJHgsIGxldmVscyA9IGFubl9uYW1lcykNCmBgYA0KDQpQbG90IFNhbmtleQ0KDQpgYGB7ciBTYW5rZXlfcGxvdCwgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MTF9DQpnZ3Bsb3QodGVzdF9nciwgaGVpZ2h0ID0gMTAsIHdpZHRoID0gMTAsIGFlcyh4LCBpZCA9IGlkLCBzcGxpdCA9IHksIHZhbHVlID0gbikpICsNCiAgZ2VvbV9wYXJhbGxlbF9zZXRzKGFlcyhmaWxsID0gQU5OMyksIGFscGhhID0gMC41LCBheGlzLndpZHRoID0gMC4xKSArDQogIGdlb21fcGFyYWxsZWxfc2V0c19heGVzKGF4aXMud2lkdGggPSAwLjIpICsNCiAgZ2VvbV9wYXJhbGxlbF9zZXRzX2xhYmVscyhjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSA1KSArDQogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTIpICsgDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbigwKSkgKyANCiAgc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQgPSBleHBhbnNpb24oMCkpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiIikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29sb3JfbGlzdCRBTk4zKSArDQogIGFubm90YXRlKGdlb20gPSAic2VnbWVudCIsIHggPSA0LjI1LCB4ZW5kID0gNC4yNSwNCiAgICAgICAgICAgeSA9IDEwLCB5ZW5kID0gNjEsIGx3ZCA9IDIpICsNCiAgYW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgeCA9IDQuMTksIHkgPSAyNSwgYW5nbGUgPSA5MCwgc2l6ZSA9IDUsDQogICAgICAgICAgIGhqdXN0ID0gMC41LCBsYWJlbCA9ICI1MCBzZWdtZW50cyIpDQpgYGANCg0KIyA0IFFDICYgUHJlLXByb2Nlc3NpbmcNCg0KU2hpZnQgY291bnRzIHRvIG9uZQ0KDQpgYGB7ciBzaGlmdF9jb3VudHN9DQojc2hpZnQgYW55IGV4cHJlc3Npb24gY291bnRzIHdpdGggYSB2YWx1ZSBvZiAwIHRvIDEgdG8gZW5hYmxlIGluIGRvd25zdHJlYW0gdHJhbnNmb3JtYXRpb25zLg0KRGF0YSA8LSBzaGlmdENvdW50c09uZShEYXRhLCB1c2VEQUxvZ2ljID0gVFJVRSkNCmBgYA0KDQojIDQuMSBTZWdtZW50IFFDDQoNCldlIGZpcnN0IGFzc2VzcyBzZXF1ZW5jaW5nIHF1YWxpdHkgYW5kIGFkZXF1YXRlIHRpc3N1ZSBzYW1wbGluZyBmb3INCmV2ZXJ5IFJPSS9BT0kgc2VnbWVudC4NCg0KRXZlcnkgUk9JL0FPSSBzZWdtZW50IHdpbGwgYmUgdGVzdGVkIGZvcjoNCg0KUmF3IHNlcXVlbmNpbmcgcmVhZHM6IHNlZ21lbnRzIHdpdGggXD4xMDAwIHJhdyByZWFkcyBhcmUgcmVtb3ZlZC4gJQ0KQWxpZ25lZCwlIFRyaW1tZWQsIG9yICUgU3RpdGNoZWQgc2VxdWVuY2luZyByZWFkczogc2VnbWVudHMgYmVsb3cgXH44MCUNCmZvciBvbmUgb3IgbW9yZSBvZiB0aGVzZSBRQyBwYXJhbWV0ZXJzIGFyZSByZW1vdmVkLiAlIFNlcXVlbmNpbmcNCnNhdHVyYXRpb24gKFsxLWRlZHVwbGljYXRlZCByZWFkcy9hbGlnbmVkIHJlYWRzXSUpOiBzZWdtZW50cyBiZWxvdyBcfjUwJQ0KcmVxdWlyZSBhZGRpdGlvbmFsIHNlcXVlbmNpbmcgdG8gY2FwdHVyZSBmdWxsIHNhbXBsZSBkaXZlcnNpdHkgYW5kIGFyZQ0Kbm90IHR5cGljYWxseSBhbmFseXplZCB1bnRpbCBpbXByb3ZlZC4gTmVnYXRpdmUgQ291bnQ6IHRoaXMgaXMgdGhlDQpnZW9tZXRyaWMgbWVhbiBvZiB0aGUgc2V2ZXJhbCB1bmlxdWUgbmVnYXRpdmUgcHJvYmVzIGluIHRoZSBHZW9NeCBwYW5lbA0KdGhhdCBkbyBub3QgdGFyZ2V0IG1STkEgYW5kIGVzdGFibGlzaCB0aGUgYmFja2dyb3VuZCBjb3VudCBsZXZlbCBwZXINCnNlZ21lbnQ7IHNlZ21lbnRzIHdpdGggbG93IG5lZ2F0aXZlIGNvdW50cyAoMS0xMCkgYXJlIG5vdCBuZWNlc3NhcmlseQ0KcmVtb3ZlZCBidXQgbWF5IGJlIHN0dWRpZWQgY2xvc2VyIGZvciBsb3cgZW5kb2dlbm91cyBnZW5lIHNpZ25hbCBhbmQvb3INCmluc3VmZmljaWVudCB0aXNzdWUgc2FtcGxpbmcuIE5vIFRlbXBsYXRlIENvbnRyb2wgKE5UQykgY291bnQ6IHZhbHVlcw0KXD4xLDAwMCBjb3VsZCBpbmRpY2F0ZSBjb250YW1pbmF0aW9uIGZvciB0aGUgc2VnbWVudHMgYXNzb2NpYXRlZCB3aXRoDQp0aGlzIE5UQzsgaG93ZXZlciwgaW4gY2FzZXMgd2hlcmUgdGhlIE5UQyBjb3VudCBpcyBiZXR3ZWVuIDEsMDAwLQ0KMTAsMDAwLCB0aGUgc2VnbWVudHMgbWF5IGJlIHVzZWQgaWYgdGhlIE5UQyBkYXRhIGlzIHVuaWZvcm1seSBsb3cgKGUuZy4NCjAtMiBjb3VudHMgZm9yIGFsbCBwcm9iZXMpLiBOdWNsZWk6IFw+MTAwIG51Y2xlaSBwZXIgc2VnbWVudCBpcw0KZ2VuZXJhbGx5IHJlY29tbWVuZGVkOyBob3dldmVyLCB0aGlzIGN1dG9mZiBpcyBoaWdobHkgc3R1ZHkvdGlzc3VlDQpkZXBlbmRlbnQgYW5kIG1heSBuZWVkIHRvIGJlIHJlZHVjZWQ7IHdoYXQgaXMgbW9zdCBpbXBvcnRhbnQgaXMNCmNvbnNpc3RlbmN5IGluIHRoZSBudWNsZWkgZGlzdHJpYnV0aW9uIGZvciBzZWdtZW50cyB3aXRoaW4gdGhlIHN0dWR5Lg0KQXJlYTogZ2VuZXJhbGx5IGNvcnJlbGF0ZXMgd2l0aCBudWNsZWk7IGEgc3RyaWN0IGN1dG9mZiBpcyBub3QgZ2VuZXJhbGx5DQphcHBsaWVkIGJhc2VkIG9uIGFyZWEuDQoNCiMgNC4xLjEgU2VsZWN0IFNlZ21lbnQgUUMNCg0KRmlyc3QsIHdlIHNlbGVjdCB0aGUgUUMgcGFyYW1ldGVyIGN1dG9mZnMsIGFnYWluc3Qgd2hpY2ggb3VyIFJPSS9BT0kNCnNlZ21lbnRzIHdpbGwgYmUgdGVzdGVkIGFuZCBmbGFnZ2VkIGFwcHJvcHJpYXRlbHkuIFdlIGhhdmUgc2VsZWN0ZWQgdGhlDQphcHByb3ByaWF0ZSBzdHVkeS1zcGVjaWZpYyBwYXJhbWV0ZXJzIGZvciB0aGlzIHN0dWR5LiBOb3RlOiB0aGUgZGVmYXVsdA0KUUMgdmFsdWVzIHJlY29tbWVuZGVkIGFib3ZlIGFyZSBhZHZpc2VkIHdoZW4gc3VydmV5aW5nIGEgbmV3IGRhdGFzZXQgZm9yDQp0aGUgZmlyc3QgdGltZS4NCg0KRGVmYXVsdCBRQyBjdXRvZmZzIGFyZSBjb21tZW50ZWQgaW4gKCkgYWRqYWNlbnQgdG8gdGhlIHJlc3BlY3RpdmUNCnBhcmFtZXRlcnMgc3R1ZHktc3BlY2lmaWMgdmFsdWVzIHdlcmUgc2VsZWN0ZWQgYWZ0ZXIgdmlzdWFsaXppbmcgdGhlIFFDDQpyZXN1bHRzIGluIG1vcmUgZGV0YWlsIGJlbG93DQoNCmBgYHtyIHNldF9RQ19wYXJhbXN9DQpRQ19wYXJhbXMgPC0NCiAgbGlzdChtaW5TZWdtZW50UmVhZHMgPSAxMDAwLCAjIE1pbmltdW0gbnVtYmVyIG9mIHJlYWRzICgxMDAwKQ0KICAgICAgIHBlcmNlbnRUcmltbWVkID0gODAsICAgICMgTWluaW11bSAlIG9mIHJlYWRzIHRyaW1tZWQgKDgwJSkNCiAgICAgICBwZXJjZW50U3RpdGNoZWQgPSA4MCwgICAjIE1pbmltdW0gJSBvZiByZWFkcyBzdGl0Y2hlZCAoODAlKQ0KICAgICAgIHBlcmNlbnRBbGlnbmVkID0gNzUsICAgICMgTWluaW11bSAlIG9mIHJlYWRzIGFsaWduZWQgKDgwJSkNCiAgICAgICBwZXJjZW50U2F0dXJhdGlvbiA9IDUwLCAjIE1pbmltdW0gc2VxdWVuY2luZyBzYXR1cmF0aW9uICg1MCUpDQogICAgICAgbWluTmVnYXRpdmVDb3VudCA9IDEsICAgIyBNaW5pbXVtIG5lZ2F0aXZlIGNvbnRyb2wgY291bnRzICgxMCkNCiAgICAgICBtYXhOVENDb3VudCA9IDkwMDAsICAgICAjIE1heGltdW0gY291bnRzIG9ic2VydmVkIGluIE5UQyB3ZWxsICgxMDAwKQ0KICAgICAgIG1pbk51Y2xlaSA9IDIwLCAgICAgICAgIyBNaW5pbXVtICMgb2YgbnVjbGVpIGVzdGltYXRlZCAoMTAwKQ0KICAgICAgIG1pbkFyZWEgPSAxMDAwKSAgICAgICAgICMgTWluaW11bSBzZWdtZW50IGFyZWEgKDUwMDApDQpEYXRhIDwtDQogIHNldFNlZ21lbnRRQ0ZsYWdzKERhdGEsIHFjQ3V0b2ZmcyA9IFFDX3BhcmFtcykgICAgICAgIA0KDQpjYXQoInByZS1RQyBmZWF0dXJlczoiLCBkaW0oRGF0YSlbMV0sICJcbnByZS1RQyBzYW1wbGVzOiIsIGRpbShEYXRhKVsyXSkNCg0KI1RhYmxlIGZvciBjbGFyaWZpY2F0aW9uDQpRQ3BhcmFtc19kZiA8LSBkYXRhLmZyYW1lICgNCiAgaXRlbXMgPSBjKCJtaW5TZWdtZW50UmVhZHMiLCJwZXJjZW50VHJpbW1lZCIsInBlcmNlbnRTdGl0Y2hlZCIsInBlcmNlbnRBbGlnbmVkIiwicGVyY2VudFNhdHVyYXRpb24iLA0KICAgICAgICAgICAgIm1pbk5lZ2F0aXZlQ291bnQiLCJtYXhOVENDb3VudCIsIm1pbk51Y2xlaSIsIm1pbkFyZWEiKSwNCiAgZGVmYXVsdHMgPSBjKDEwMDAsODAsODAsODAsNTAsMTAsMTAwMCwxMDAsNTAwMCksDQogIGFjdHVhbCA9IGMoUUNfcGFyYW1zJG1pblNlZ21lbnRSZWFkcyxRQ19wYXJhbXMkcGVyY2VudFRyaW1tZWQsUUNfcGFyYW1zJHBlcmNlbnRTdGl0Y2hlZCxRQ19wYXJhbXMkcGVyY2VudEFsaWduZWQsUUNfcGFyYW1zJHBlcmNlbnRTYXR1cmF0aW9uLFFDX3BhcmFtcyRtaW5OZWdhdGl2ZUNvdW50LFFDX3BhcmFtcyRtYXhOVENDb3VudCxRQ19wYXJhbXMkbWluTnVjbGVpLFFDX3BhcmFtcyRtaW5BcmVhKQ0KKQ0KDQpkYXRhdGFibGUoUUNwYXJhbXNfZGYsIHJvd25hbWVzPUZBTFNFLA0KICAgICAgICAgIGNhcHRpb24gPSAiUUMgdGhyZXNob2xkcyIsDQogICAgICAgICAgZXh0ZW5zaW9ucyA9ICdCdXR0b25zJywgb3B0aW9ucyA9IGxpc3QgKA0KICAgICAgICAgICAgZG9tID0gJ0JmdHJpcCcsDQogICAgICAgICAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JykNCiAgICAgICAgICApDQopDQoNCmBgYA0KDQpDb2xsYXRlIFFDIFJlc3VsdHMNCg0KYGBge3IgY29sbGF0ZV9RQ19yZXN1bHRzfQ0KUUNSZXN1bHRzIDwtIHByb3RvY29sRGF0YShEYXRhKVtbIlFDRmxhZ3MiXV0NCmZsYWdfY29sdW1ucyA8LSBjb2xuYW1lcyhRQ1Jlc3VsdHMpDQpRQ19TdW1tYXJ5IDwtIGRhdGEuZnJhbWUoUGFzcyA9IGNvbFN1bXMoIVFDUmVzdWx0c1ssIGZsYWdfY29sdW1uc10pLA0KICAgICAgICAgICAgICAgICAgICAgICAgIFdhcm5pbmcgPSBjb2xTdW1zKFFDUmVzdWx0c1ssIGZsYWdfY29sdW1uc10pKQ0KDQpRQ1Jlc3VsdHMkUUNTdGF0dXMgPC0gYXBwbHkoUUNSZXN1bHRzLCAxTCwgZnVuY3Rpb24oeCkgew0KICBpZmVsc2Uoc3VtKHgpID09IDBMLCAiUEFTUyIsICJXQVJOSU5HIikNCn0pDQoNClFDX1N1bW1hcnlbIlRPVEFMIEZMQUdTIiwgXSA8LQ0KICBjKHN1bShRQ1Jlc3VsdHNbLCAiUUNTdGF0dXMiXSA9PSAiUEFTUyIpLA0KICAgIHN1bShRQ1Jlc3VsdHNbLCAiUUNTdGF0dXMiXSA9PSAiV0FSTklORyIpKQ0KDQpjb2xfYnkgPC0gIkFOTjEiDQpjb2xfYnlfcGxhdGUgPC0gIlBsYXRlX0lEIg0KYGBgDQoNCiMgNC4yIEdyYXBoaWNhbCBzdW1tYXJpZXMgb2YgUUMgc3RhdGlzdGljcyB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQpVc2UgdGhlIHRhYi1tZW51IHRvIG5hdmlnYXRlIQ0KDQpgYGB7ciBRQ19wbG90dGluZ30NClFDX2hpc3RvZ3JhbSA8LSBmdW5jdGlvbihhc3NheV9kYXRhID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsX2J5ID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICB0aHIgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX3RyYW5zID0gTlVMTCkgew0KICBwbHQgPC0gZ2dwbG90KGFzc2F5X2RhdGEsDQogICAgICAgICAgICAgICAgYWVzX3N0cmluZyh4ID0gcGFzdGUwKCJ1bmxpc3QoYCIsIGFubm90YXRpb24sICJgKSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGZpbGxfYnkpKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDUwKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gdGhyLCBsdHkgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmxhY2siKSArDQogICAgdGhlbWVfYncoKSArIGd1aWRlcyhmaWxsID0gIm5vbmUiKSArDQogICAgZmFjZXRfd3JhcChhcy5mb3JtdWxhKHBhc3RlKCJ+IiwgZmlsbF9ieSkpLCBucm93ID0gNCkgKw0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jb2xvcl9saXN0JEFOTjEpICsNCiAgICBsYWJzKHggPSBhbm5vdGF0aW9uLCB5ID0gInNlZ21lbnRzLCAjIiwgdGl0bGUgPSBhbm5vdGF0aW9uKQ0KICBpZighaXMubnVsbChzY2FsZV90cmFucykpIHsNCiAgICBwbHQgPC0gcGx0ICsNCiAgICAgIHNjYWxlX3hfY29udGludW91cyh0cmFucyA9IHNjYWxlX3RyYW5zKQ0KICB9DQogIHBsdA0KfQ0KYGBgDQoNCiMjIFRyaW1tZWQNCg0KYGBge3J9DQpRQ19oaXN0b2dyYW0oc0RhdGEoRGF0YSksICJUcmltbWVkICglKSIsIGNvbF9ieSwgUUNfcGFyYW1zJHBlcmNlbnRUcmltbWVkKQ0KYGBgDQoNCiMjIFN0aWNoZWQgKCUpDQoNCmBgYHtyfQ0KUUNfaGlzdG9ncmFtKHNEYXRhKERhdGEpLCAiU3RpdGNoZWQgKCUpIiwgY29sX2J5LCBRQ19wYXJhbXMkcGVyY2VudFN0aXRjaGVkKQ0KYGBgDQoNCiMjIEFsaWduZWQgKCUpDQoNCmBgYHtyfQ0KUUNfaGlzdG9ncmFtKHNEYXRhKERhdGEpLCAiQWxpZ25lZCAoJSkiLCBjb2xfYnksUUNfcGFyYW1zJHBlcmNlbnRBbGlnbmVkKQ0KYGBgDQoNCiMjIFNlcXVlbmNpbmcgU2F0dXJhdGlvbiAoJSkgey5hY3RpdmV9DQoNCmBgYHtyfQ0KUUNfaGlzdG9ncmFtKHNEYXRhKERhdGEpLCAiU2F0dXJhdGVkICglKSIsIGNvbF9ieSwgUUNfcGFyYW1zJHBlcmNlbnRTYXR1cmF0aW9uKSArDQogIGxhYnModGl0bGUgPSAiU2VxdWVuY2luZyBTYXR1cmF0aW9uICglKSIsDQogICAgICAgeCA9ICJTZXF1ZW5jaW5nIFNhdHVyYXRpb24gKCUpIikNCmBgYA0KDQojIyBBcmVhDQoNCmBgYHtyfQ0KUUNfaGlzdG9ncmFtKHNEYXRhKERhdGEpLCAiYXJlYSIsIGNvbF9ieSwgUUNfcGFyYW1zJG1pbkFyZWEsIHNjYWxlX3RyYW5zID0gImxvZzEwIikNCmBgYA0KDQojIyBOdWNsZWkgY291bnQNCg0KYGBge3J9DQpRQ19oaXN0b2dyYW0oc0RhdGEoRGF0YSksICJudWNsZWkiLCBjb2xfYnksIFFDX3BhcmFtcyRtaW5OdWNsZWkpDQpgYGANCg0KIyMgRHVwbGljYXRpb25SYXRlDQoNCmBgYHtyfQ0KZ2dwbG90KHBEYXRhKHByb3RvY29sRGF0YShEYXRhKSksDQogICAgICAgYWVzKHggPSBQbGF0ZV9JRCwgZmlsbD1QbGF0ZV9JRCwNCiAgICAgICAgICB5ID0gKERlZHVwbGljYXRlZFJlYWRzL1JhdykpKSArDQogIGdlb21fdmlvbGluKCkgKw0KICBnZW9tX2ppdHRlcih3aWR0aCA9IC4yKSArDQogIGxhYnMoeSA9ICJEZWR1cGxpY2F0ZWQgLyBSYXcgcmVhZHMiKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsNCiAgdGhlbWVfYncoKQ0KDQojIFBsYXRlX0lEIHRvIGNoZWNrIHRoZSBsb3cgZHViL3JhdyBjb3VudA0KUUNfaGlzdG9ncmFtKHNEYXRhKERhdGEpLCAiU2F0dXJhdGVkICglKSIsIGNvbF9ieV9wbGF0ZSwgUUNfcGFyYW1zJHBlcmNlbnRTYXR1cmF0aW9uKSArDQogIGxhYnModGl0bGUgPSAiU2VxdWVuY2luZyBTYXR1cmF0aW9uICglKSIsDQogICAgICAgeCA9ICJTZXF1ZW5jaW5nIFNhdHVyYXRpb24gKCUpIikNCg0KYGBgDQoNCiMjIE5lZ3Byb2JlcyB2cyBFbmRvZ2Vub3VzDQoNCmBgYHtyIHBsb3RfbmVncHJvYmVfZGF0YSwgZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9NX0NCnRtcF90YXJnZXRfRGF0YSA8LSBhZ2dyZWdhdGVDb3VudHMoRGF0YSkNCg0KI2dldCBuZWdhdGl2ZSBwcm9iZSBkYXRhDQpuZWdzPC1zdWJzZXQodG1wX3RhcmdldF9EYXRhLENvZGVDbGFzcz09Ik5lZ2F0aXZlIikNCg0KcDE8LWdncGxvdChwRGF0YShuZWdzKSwNCiAgICAgICBhZXMoeCA9IEFOTjEsIGZpbGwgPSBBTk4xLA0KICAgICAgICAgIHkgPSBhc3NheURhdGFFbGVtZW50KG5lZ3MsIGVsdCA9ICJleHBycyIpKSkgKw0KICBnZW9tX3Zpb2xpbigpICsNCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAuMikgKw0KICBsYWJzKHkgPSAiTmVnYXRpdmUgcHJvYmVzIEV4cHJlc3Npb24iKSArDQogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDEsMzAwMCksIHRyYW5zID0gImxvZzIiKSArDQogIHRoZW1lX2J3KCkNCg0KDQojIGdldCBlbmRvZ2Vub3VzIHByb2JlIGRhdGENCmVuZDwtc3Vic2V0KHRtcF90YXJnZXRfRGF0YSxDb2RlQ2xhc3M9PSJFbmRvZ2Vub3VzIikNCg0KcDI8LWdncGxvdChwRGF0YShlbmQpLA0KICAgICAgIGFlcyh4ID0gQU5OMSwgZmlsbCA9IEFOTjEsDQogICAgICAgICAgIHkgPSBjb2xNZWFucyhhc3NheURhdGFFbGVtZW50KGVuZCwgZWx0ID0gImV4cHJzIikpKSkgKw0KICBnZW9tX3Zpb2xpbigpICsNCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAuMikgKw0KICBsYWJzKHkgPSAiRW5kb2dlbm91cyBwcm9iZXMgRXhwcmVzc2lvbiAobWVhbikiKSArDQogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDEsMzAwMCksdHJhbnMgPSAibG9nMiIpICsNCiAgdGhlbWVfYncoKQ0KDQpwbCA8LWxpc3QocDEscDIpDQpwbG90X2dyaWQocGxvdGxpc3Q9cGwsIG5yb3c9MSwgYWxpZ249J2gnKQ0KDQpgYGANCg0KIyMgTmVnX3Byb2JlIHJlYWRzIGNvbXBhcmVkIHRvIHJhd19yZWFkcw0KDQpgYGB7cn0NCg0KIyBtYWtlIGJhY2tncm91bmQgdG90YWwgbmVnIHByb2JlIGNvdW50DQpmZGF0YV9kZjwtZkRhdGEoRGF0YSkNCm5lZ3Byb2Jlc25hbWVzPC1yb3duYW1lcyhmZGF0YV9kZltmZGF0YV9kZiROZWdhdGl2ZT09VFJVRSxdKQ0KdGVtcF9leHA8LWFzc2F5RGF0YUVsZW1lbnQoRGF0YSxlbHQ9J2V4cHJzJykNCm5lZ3Byb2JlX2V4cHJfZmQ8LXRlbXBfZXhwW3Jvd25hbWVzKHRlbXBfZXhwKSAlaW4lIG5lZ3Byb2Jlc25hbWVzLF0NCnRvdF9uZWdfY3RybF9yZWFkczwtY29sU3VtcyhuZWdwcm9iZV9leHByX2ZkKQ0KdG90X2RlZHVwX3JlYWRzPC1wRGF0YShwcm90b2NvbERhdGEoRGF0YSkpJERlZHVwbGljYXRlZFJlYWRzDQoNCmRmPC1kYXRhLmZyYW1lKCdhb2knPSBuYW1lcyh0b3RfbmVnX2N0cmxfcmVhZHMpLCd0b3RfZGVkdXBfcmVhZHMnID0gYXMubnVtZXJpYyh0b3RfZGVkdXBfcmVhZHMpLCd0b3RfbmVnX2N0cmxfcmVhZHMnPWFzLm51bWVyaWModG90X25lZ19jdHJsX3JlYWRzKSkNCmRmPC1tZWx0KGRmLGlkPSJhb2kiKQ0KZ2dwbG90KGRmLGFlcyhmaWxsPXZhcmlhYmxlLHk9dmFsdWUseD1hb2kpKSArIA0KICBnZW9tX2Jhcihwb3NpdGlvbj0iaWRlbnRpdHkiLHN0YXQ9ImlkZW50aXR5IikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSBsb2cyX3RyYW5zKCkpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCkpICAgICAgICAgICAgICAgICAgICAgDQogDQpgYGANCg0KIyMgRHVwbGljYXRlZCByZWFkcyB2cyBCYWNrZ3JvdW5kDQoNCmBgYHtyfQ0KIyBnZXQgZGNjIHBlciBwbGF0ZS4gc3VtIG5lZ3Byb2JlIGNvdW50cy9kY2MvcGxhdGUNCmdncGxvdChwRGF0YShwcm90b2NvbERhdGEoRGF0YSkpLA0KICAgICAgIGFlcyh4ID0gUGxhdGVfSUQsIGZpbGw9UGxhdGVfSUQsDQogICAgICAgICAgeSA9IERlZHVwbGljYXRlZFJlYWRzKSkgKw0KICBnZW9tX3Zpb2xpbigpICsNCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAuMikgKw0KICBsYWJzKHkgPSAiRGVkdXBsaWNhdGVkIC8gUmF3IHJlYWRzIikgKw0KICBzY2FsZV95X2xvZzEwKCkrDQogIGdlb21faGxpbmUoZGF0YSA9cERhdGEocHJvdG9jb2xEYXRhKERhdGEpKSAsIA0KICAgICAgICAgICBhZXMoeWludGVyY2VwdCA9IE5UQywgY29sb3VyPVBsYXRlX0lEKSkgKw0KICB0aGVtZV9idygpDQoNCmBgYA0KDQojIyBEdXBsaWNhdGVkIHJlYWRzIHZzIFJPSWFyZWENCg0KYGBge3IsIGZpZy53aWR0aD0xNSxmaWcuaGVpZ2h0PTV9DQp0ZW1wX2RmPC1jYmluZChwRGF0YShEYXRhKSxwRGF0YShwcm90b2NvbERhdGEoRGF0YSkpLGRjYz1yb3duYW1lcyhwRGF0YShEYXRhKSkpDQoNCmdncGxvdCh0ZW1wX2RmLA0KICAgICAgIGFlcyh4ID0gZGNjLCBjb2xvdXI9c2xpZGVfbmFtZSwNCiAgICAgICAgICB5ID0gKERlZHVwbGljYXRlZFJlYWRzL2FyZWEpICkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgeWxpbSgwLDIwKSArIA0KICBsYWJzKHkgPSAiRGVkdXBsaWNhdGVkIHJlYWRzIC8gUk9JIGFyZWEiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPTYsIGFuZ2xlPTkwLCBoanVzdD0xKSApDQoNCmBgYA0KDQojIyBEdXBsaWNhdGVkIHJlYWRzIHZzIG51Y2xlaQ0KDQpgYGB7ciwgZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9NX0NCnRlbXBfZGY8LWNiaW5kKHBEYXRhKERhdGEpLHBEYXRhKHByb3RvY29sRGF0YShEYXRhKSksZGNjPXJvd25hbWVzKHBEYXRhKERhdGEpKSkNCg0KZ2dwbG90KHRlbXBfZGYsDQogICAgICAgYWVzKHggPSBkY2MsIGNvbG91cj1zbGlkZV9uYW1lLA0KICAgICAgICAgIHkgPSAoRGVkdXBsaWNhdGVkUmVhZHMvbnVjbGVpKSApKSArDQogIGdlb21fcG9pbnQoKSArDQogIHlsaW0oMCwyMDApICsNCiAgbGFicyh5ID0gIkRlZHVwbGljYXRlZCByZWFkcyAvIG51Y2xlaSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9NiwgYW5nbGU9OTAsIGhqdXN0PTEpICkNCg0KYGBgDQoNCiMgNC4zIFByb2Nlc3MgTmVnYXRpdmUgR2VvTWVhbnMNCg0KYGBge3J9DQojIENhbGN1bGF0ZSB0aGUgbmVnYXRpdmUgZ2VvbWV0cmljIG1lYW5zIGZvciBlYWNoIG1vZHVsZQ0KIyBJdCB3aWxsIHNob3cgb25seSB0aGUgbmVnYXRpdmUgcHJvYmVzIGdlb21lYW4sIHNvIGV4cGVjdCBsZXNzIHNlZ21lbnRzLg0KbmVnYXRpdmVHZW9NZWFucyA8LSANCiAgZXNCeShuZWdhdGl2ZUNvbnRyb2xTdWJzZXQoRGF0YSksIA0KICAgICAgIEdST1VQID0gIk1vZHVsZSIsIA0KICAgICAgIEZVTiA9IGZ1bmN0aW9uKHgpIHsgDQogICAgICAgICBhc3NheURhdGFBcHBseSh4LCBNQVJHSU4gPSAyLCBGVU4gPSBuZ2VvTWVhbiwgZWx0ID0gImV4cHJzIikgDQogICAgICAgfSkgDQpwcm90b2NvbERhdGEoRGF0YSlbWyJOZWdHZW9NZWFuIl1dIDwtIG5lZ2F0aXZlR2VvTWVhbnMNCg0KbmVnQ29scyA8LSBwYXN0ZTAoIk5lZ0dlb01lYW5fIiwgbW9kdWxlcykNCnBEYXRhKERhdGEpWywgbmVnQ29sc10gPC0gc0RhdGEoRGF0YSlbWyJOZWdHZW9NZWFuIl1dDQpmb3IoYW5uIGluIG5lZ0NvbHMpIHsNCiAgcGx0IDwtIFFDX2hpc3RvZ3JhbShwRGF0YShEYXRhKSwgYW5uLCBjb2xfYnksIDIsIHNjYWxlX3RyYW5zID0gImxvZzEwIikNCiAgcHJpbnQocGx0KQ0KfQ0KDQoNCiMgRGV0YXRjaCBuZWdfZ2VvbWVhbiBjb2x1bW5zIGFoZWFkIG9mIGFnZ3JlZ2F0ZUNvdW50cyBjYWxsDQoNCnBEYXRhKERhdGEpIDwtIHBEYXRhKERhdGEpWywgIWNvbG5hbWVzKHBEYXRhKERhdGEpKSAlaW4lIG5lZ0NvbHNdDQoNCmBgYA0KDQpTaG93IGFsbCBOVEMgdmFsdWVzLCBGcmVxID0gXCMgb2YgU2VnbWVudHMgd2l0aCBhIGdpdmVuIE5UQyBjb3VudDoNCg0KYGBge3IgUUNfdGFibGVzfQ0KUUM8LXNEYXRhKERhdGEpDQoNCm50Y19kZiA8LVFDWyxjKCJzbGlkZV9uYW1lIiwiUGxhdGVfSUQiLCJOVENfSUQiLCJOVEMiKV0NCnRlbXB0YWJsZTwtbnRjX2RmICU+JSBkcGx5cjo6Y291bnQobnRjX2RmJHNsaWRlX25hbWUsbnRjX2RmJE5UQ19JRCxudGNfZGYkUGxhdGVfSUQsbnRjX2RmJE5UQykNCmNvbG5hbWVzKHRlbXB0YWJsZSkgPC0gYygiU2xpZGVfbmFtZSIsIk5UQ19JRCIsIlBsYXRlX0lEIiwiTlRDX2NvdW50IiwiTnVtYmVyX29mX3NhbXBsZXMiKQ0KZGF0YXRhYmxlKHRlbXB0YWJsZSwgcm93bmFtZXMgPSBGQUxTRSkNCg0KDQprYWJsZSh0YWJsZShOVENfQ291bnQgPSBzRGF0YShEYXRhKSROVEMpLCBjb2wubmFtZXMgPSBjKCJOVEMgQ291bnQiLCAiIyBvZiBTZWdtZW50cyIpKQ0KDQprYWJsZShRQ19TdW1tYXJ5LCBjYXB0aW9uID0gIlFDIFN1bW1hcnkgVGFibGUgZm9yIGVhY2ggU2VnbWVudCIpDQoNCmRhdGF0YWJsZShRQ19TdW1tYXJ5LA0KICAgICAgICAgIGNhcHRpb24gPSAiQU9JIFFDIFN1bW1hcnkiLA0KICAgICAgICAgIGV4dGVuc2lvbnMgPSAnQnV0dG9ucycsIG9wdGlvbnMgPSBsaXN0ICgNCiAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgYnV0dG9ucyA9IGMoJ2NvcHknLCAnY3N2JywgJ2V4Y2VsJywgJ3BkZicsICdwcmludCcpDQogICAgICAgICAgKQ0KKQ0KYGBgDQoNClNob3cgQU9JcyB3aGljaCBmYWlsIGNyaXRpY2FsIFFDcy4NCg0KYGBge3IgbGlzdF9mYWlsdXJlc30NClFDPC1zRGF0YShEYXRhKQ0KdW5kZXJzYXQ8LXN1YnNldChRQywgYFNhdHVyYXRlZCAoJSlgPD0gUUNfcGFyYW1zJHBlcmNlbnRTYXR1cmF0aW9uKQ0KDQppZihucm93KHVuZGVyc2F0KT4gMCkgew0KDQpkYXRhdGFibGUoYWdncmVnYXRlKHVuZGVyc2F0LCBieT1saXN0KHVuZGVyc2F0JFNhbXBsZUlEKSxwYXN0ZSxjb2xsYXBzZT0iOyIpLA0KICAgICAgICAgIGV4dGVuc2lvbnMgPSAnQnV0dG9ucycsIG9wdGlvbnMgPSBsaXN0ICgNCiAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgYnV0dG9ucyA9IGMoJ2NvcHknLCAnY3N2JywgJ2V4Y2VsJywgJ3BkZicsICdwcmludCcpDQogICAgICAgICAgKQ0KKX0NCmBgYA0KDQpTdWJzZXR0aW5nIG91ciBkYXRhc2V0IGhhcyByZW1vdmVkIHNhbXBsZXMgd2hpY2ggZGlkIG5vdCBwYXNzIFFDDQoNCmBgYHtyIHN1YnNldHRpbmdfUUNfZmFpbHN9DQpEYXRhIDwtIERhdGFbLCBRQ1Jlc3VsdHMkUUNTdGF0dXMgPT0gIlBBU1MiXQ0KYGBgDQoNCkdlbmVyYWxseSBrZWVwIHRoZSBxY0N1dG9mZnMgcGFyYW1ldGVycyB1bmNoYW5nZWQuIFNldA0KcmVtb3ZlTG9jYWxPdXRsaWVycyB0byBGQUxTRSBpZiB5b3UgZG8gbm90IHdhbnQgdG8gcmVtb3ZlIGxvY2FsIG91dGxpZXJzDQoNCmBgYHtyIHByb2Nlc3NfUUN9DQpEYXRhIDwtIHNldEJpb1Byb2JlUUNGbGFncyhEYXRhLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxY0N1dG9mZnMgPSBsaXN0KG1pblByb2JlUmF0aW8gPSAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwZXJjZW50RmFpbEdydWJicyA9IDIwKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlTG9jYWxPdXRsaWVycyA9IEZBTFNFKQ0KDQpQcm9iZVFDUmVzdWx0cyA8LSBmRGF0YShEYXRhKVtbIlFDRmxhZ3MiXV0NCmBgYA0KDQpEZWZpbmUgUUMgdGFibGUgZm9yIFByb2JlIFFDDQoNCmBgYHtyIGRlZmluZV9xY190YWJsZX0NCnFjX2RmIDwtIGRhdGEuZnJhbWUoUGFzc2VkID0gc3VtKHJvd1N1bXMoUHJvYmVRQ1Jlc3VsdHNbLCAtMV0pID09IDApLA0KICAgICAgICAgICAgICAgICAgICBHbG9iYWwgPSBzdW0oUHJvYmVRQ1Jlc3VsdHMkR2xvYmFsR3J1YmJzT3V0bGllciksDQogICAgICAgICAgICAgICAgICAgIExvY2FsID0gc3VtKHJvd1N1bXMoUHJvYmVRQ1Jlc3VsdHNbLCAtMjotMV0pID4gMA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmICFQcm9iZVFDUmVzdWx0cyRHbG9iYWxHcnViYnNPdXRsaWVyKSkNCmBgYA0KDQpTdWJzZXQgb2JqZWN0IHRvIGV4Y2x1ZGUgYWxsIHRoYXQgZGlkIG5vdCBwYXNzIFJhdGlvICYgR2xvYmFsIHRlc3RpbmcNCg0KYGBge3Igc3Vic2V0fQ0KUHJvYmVRQ1Bhc3NlZCA8LSANCiAgc3Vic2V0KERhdGEsIA0KICAgICAgICAgZkRhdGEoRGF0YSlbWyJRQ0ZsYWdzIl1dWyxjKCJMb3dQcm9iZVJhdGlvIildID09IEZBTFNFICYNCiAgICAgICAgICAgZkRhdGEoRGF0YSlbWyJRQ0ZsYWdzIl1dWyxjKCJHbG9iYWxHcnViYnNPdXRsaWVyIildID09IEZBTFNFKQ0KDQpEYXRhIDwtIFByb2JlUUNQYXNzZWQgDQpjYXQoIkFmdGVyIFFDIGZlYXR1cmVzOiIsIGRpbShEYXRhKVsxXSwgIlxuQWZ0ZXIgUUMgc2FtcGxlczoiLCBkaW0oRGF0YSlbMl0pDQpgYGANCg0KQ2hlY2sgaG93IG1hbnkgdW5pcXVlIHRhcmdldHMgdGhlIG9iamVjdCBoYXMNCg0KYGBge3IgdW5pcXVlX2NoZWNrfQ0KbGVuZ3RoKHVuaXF1ZShmZWF0dXJlRGF0YShEYXRhKVtbIlRhcmdldE5hbWUiXV0pKQ0KYGBgDQoNCkNvbGxhcHNlIHRvIHRhcmdldHMNCg0KYGBge3IgY29sbGFwc190YXJnZXRzfQ0KdGFyZ2V0X0RhdGEgPC0gYWdncmVnYXRlQ291bnRzKERhdGEpDQoNCmV4cHJzKHRhcmdldF9EYXRhKVsxOjUsIDE6Ml0NCmBgYA0KDQpEZWZpbmUgTE9RIFNEIHRocmVzaG9sZCBhbmQgbWluaW11bSB2YWx1ZQ0KDQpgYGB7ciBzZXRfTFNRfQ0KY3V0b2ZmIDwtIDINCm1pbkxPUSA8LSAyDQpgYGANCg0KIyA0LjQgTGltaXQgb2YgUXVhbnRpZmljYXRpb24NCg0KV2UgZGVmaW5lIGEgbGltaXQgb2YgcXVhbnRpZmljYXRpb24gKExPUSkgcGVyIFJPSS9BT0kgc2VnbWVudCBiYXNlZCBvbg0KdGhlIG5lZ2F0aXZlIGNvbnRyb2wgcHJvYmVzIHRvIGd1aWRlIHRoZSBmaWx0ZXJpbmcgb2Ygc2VnbWVudHMgYW5kIGdlbmVzDQp3aXRoIGxvdyBzaWduYWwgcmVsYXRpdmUgdG8gYmFja2dyb3VuZC4gVGhlIGZvcm11bGEgZm9yIGNhbGN1bGF0aW5nIHRoZQ0KTE9RIGluIHRoZSAkaV57dGh9JCBzZWdtZW50IGF0ICRuJCBzdGFuZGFyZCBkZXZpYXRpb25zICgkbiA9IDIkIGZvciB0aGlzDQpzdHVkeSkgaXM6ICRMT1FfaT1nZW9tZWFuKE5lZ1Byb2JlX2kpKmdlb1NEKE5lZ1Byb2JlX2kpXm4kDQoNCkNhbGN1bGF0ZSBMT1EgcGVyIG1vZHVsZSB0ZXN0ZWQNCg0KYGBge3IgY2FsY3VsYXRlX0xPUX0NCkxPUSA8LSBkYXRhLmZyYW1lKHJvdy5uYW1lcyA9IGNvbG5hbWVzKHRhcmdldF9EYXRhKSkNCmZvcihtb2R1bGUgaW4gbW9kdWxlcykgew0KICB2YXJzIDwtIHBhc3RlMChjKCJOZWdHZW9NZWFuXyIsICJOZWdHZW9TRF8iKSwNCiAgICAgICAgICAgICAgICAgbW9kdWxlKQ0KICBpZihhbGwodmFyc1sxOjJdICVpbiUgY29sbmFtZXMocERhdGEodGFyZ2V0X0RhdGEpKSkpIHsNCiAgICBMT1FbLCBtb2R1bGVdIDwtDQogICAgICBwbWF4KG1pbkxPUSwNCiAgICAgICAgICAgcERhdGEodGFyZ2V0X0RhdGEpWywgdmFyc1sxXV0gKiANCiAgICAgICAgICAgICBwRGF0YSh0YXJnZXRfRGF0YSlbLCB2YXJzWzJdXSBeIGN1dG9mZikNCiAgfQ0KfQ0KcERhdGEodGFyZ2V0X0RhdGEpJExPUSA8LSBMT1ENCmBgYA0KDQojIDQuNSBGaWx0ZXJpbmcNCg0KQWZ0ZXIgZGV0ZXJtaW5pbmcgdGhlIGxpbWl0IG9mIHF1YW50aWZpY2F0aW9uIChMT1EpIHBlciBzZWdtZW50LCB3ZQ0KcmVjb21tZW5kIGZpbHRlcmluZyBvdXQgZWl0aGVyIHNlZ21lbnRzIGFuZC9vciBnZW5lcyB3aXRoIGFibm9ybWFsbHkgbG93DQpzaWduYWwuIEZpbHRlcmluZyBpcyBhbiBpbXBvcnRhbnQgc3RlcCB0byBmb2N1cyBvbiB0aGUgdHJ1ZSBiaW9sb2dpY2FsDQpkYXRhIG9mIGludGVyZXN0Lg0KDQpXZSBkZXRlcm1pbmUgdGhlIG51bWJlciBvZiBnZW5lcyBkZXRlY3RlZCBpbiBlYWNoIHNlZ21lbnQgYWNyb3NzIHRoZQ0KZGF0YXNldC4NCg0KYGBge3IgZmlsdGVyaW5nfQ0KTE9RX01hdCA8LSBjKCkNCmZvcihtb2R1bGUgaW4gbW9kdWxlcykgew0KICBpbmQgPC0gZkRhdGEodGFyZ2V0X0RhdGEpJE1vZHVsZSA9PSBtb2R1bGUNCiAgTWF0X2kgPC0gdChlc0FwcGx5KHRhcmdldF9EYXRhW2luZCwgXSwgTUFSR0lOID0gMSwNCiAgICAgICAgICAgICAgICAgICAgIEZVTiA9IGZ1bmN0aW9uKHgpIHsNCiAgICAgICAgICAgICAgICAgICAgICAgeCA+IExPUVssIG1vZHVsZV0NCiAgICAgICAgICAgICAgICAgICAgIH0pKQ0KICBMT1FfTWF0IDwtIHJiaW5kKExPUV9NYXQsIE1hdF9pKQ0KfQ0KIyBlbnN1cmUgb3JkZXJpbmcgc2luY2UgdGhpcyBpcyBzdG9yZWQgb3V0c2lkZSBvZiB0aGUgZ2VvbXhTZXQNCkxPUV9NYXQgPC0gTE9RX01hdFtmRGF0YSh0YXJnZXRfRGF0YSkkVGFyZ2V0TmFtZSwgXQ0KYGBgDQoNCiMgNC41LjEgU2VnbWVudCBHZW5lIERldGVjdGlvbg0KDQpXZSBmaXJzdCBmaWx0ZXIgb3V0IHNlZ21lbnRzIHdpdGggZXhjZXB0aW9uYWxseSBsb3cgc2lnbmFsLiBUaGVzZQ0Kc2VnbWVudHMgd2lsbCBoYXZlIGEgc21hbGwgZnJhY3Rpb24gb2YgcGFuZWwgZ2VuZXMgZGV0ZWN0ZWQgYWJvdmUgdGhlDQpMT1EgcmVsYXRpdmUgdG8gdGhlIG90aGVyIHNlZ21lbnRzIGluIHRoZSBzdHVkeS4gTGV0J3MgdmlzdWFsaXplIHRoZQ0KZGlzdHJpYnV0aW9uIG9mIHNlZ21lbnRzIHdpdGggcmVzcGVjdCB0byB0aGVpciAlIGdlbmVzIGRldGVjdGVkOg0KDQpTYXZlIGRldGVjdGlvbiByYXRlIGluZm9ybWF0aW9uIHRvIHBoZW5vIGRhdGENCg0KYGBge3Igc2F2ZV9kZXRlY3Rpbm9fcmF0ZX0NCnBEYXRhKHRhcmdldF9EYXRhKSRHZW5lc0RldGVjdGVkIDwtIA0KICBjb2xTdW1zKExPUV9NYXQsIG5hLnJtID0gVFJVRSkNCnBEYXRhKHRhcmdldF9EYXRhKSRHZW5lRGV0ZWN0aW9uUmF0ZSA8LQ0KICBwRGF0YSh0YXJnZXRfRGF0YSkkR2VuZXNEZXRlY3RlZCAvIG5yb3codGFyZ2V0X0RhdGEpDQoNCmBgYA0KDQpEZXRlcm1pbmUgZGV0ZWN0aW9uIHRocmVzaG9sZHM6IDElLCA1JSwgMTAlLCAxNSUsIFw+MTUlDQoNCmBgYHtyIGRldGVybWluZSt0aHJlc2hvbGRzfQ0KcERhdGEodGFyZ2V0X0RhdGEpJERldGVjdGlvblRocmVzaG9sZCA8LSANCiAgY3V0KHBEYXRhKHRhcmdldF9EYXRhKSRHZW5lRGV0ZWN0aW9uUmF0ZSwNCiAgICAgIGJyZWFrcyA9IGMoMCwgMC4wMSwgMC4wNSwgMC4xLCAwLjE1LCAwLjIsMSksDQogICAgICBsYWJlbHMgPSBjKCI8MSUiLCAiMS01JSIsICI1LTEwJSIsICIxMC0xNSUiLCAiMTUtMjAlIiwgIj4yMCUiKSkNCg0KIyBzdGFja2VkIGJhciBwbG90IG9mIGRpZmZlcmVudCBjdXQgcG9pbnRzICgxJSwgNSUsIDEwJSwgMTUlKQ0KZ2dwbG90KHBEYXRhKHRhcmdldF9EYXRhKSwNCiAgICAgICBhZXMoeCA9IERldGVjdGlvblRocmVzaG9sZCkpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBBTk4xKSkgKw0KICBnZW9tX3RleHQoc3RhdCA9ICJjb3VudCIsIGFlcyhsYWJlbCA9IC4uY291bnQuLiksIHZqdXN0ID0gLTAuNSkgKw0KICB0aGVtZV9idygpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwLjEpKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29sb3JfbGlzdCRBTk4xKSArDQogIGxhYnMoeCA9ICJHZW5lIERldGVjdGlvbiBSYXRlIiwNCiAgICAgICB5ID0gIlNlZ21lbnRzLCAjIiwNCiAgICAgICBmaWxsID0gIlNlZ21lbnQgVHlwZSIpDQpgYGANCg0KY3V0IHBlcmNlbnQgZ2VuZXMgZGV0ZWN0ZWQgYXQgMSwgNSwgMTAsIDE1DQoNCmBgYHtyIGN1dF90b19wZXJjZW50fQ0Ka2FibGUodGFibGUocERhdGEodGFyZ2V0X0RhdGEpJERldGVjdGlvblRocmVzaG9sZCwNCiAgICAgICAgICAgIHBEYXRhKHRhcmdldF9EYXRhKSRBTk4xKSkNCg0KIyBzZXQgdGhyZXNob2xkIGZvciBkZXRlY3Rpb25sZXZlbA0KIyBkZWZhdWx0IDAuMQ0KZ2VuZV9kZXRfdGhyZXNob2xkIDwtIDAuMDUNCg0KdGFyZ2V0X0RhdGEgPC0NCiAgdGFyZ2V0X0RhdGFbLCBwRGF0YSh0YXJnZXRfRGF0YSkkR2VuZURldGVjdGlvblJhdGUgPj0gZ2VuZV9kZXRfdGhyZXNob2xkXQ0KDQpkaW0odGFyZ2V0X0RhdGEpDQpgYGANCg0KIyA0LjUuMiBjb2xsZWN0IGFubm90YXRpb25zDQoNCmBgYHtyIHNlbGVjdF9hbm5vdGF0aW9uczJ9DQoNCiMgKipTZWxlY3QgdGhlIGFubm90YXRpb25zIHdlIHdhbnQgdG8gc2hvdywgdXNlIGBgIHRvIHN1cnJvdW5kIGNvbHVtbiBuYW1lcyB3aXRoIHNwYWNlcyBvciBzcGVjaWFsIHN5bWJvbHMqKg0Kb2xkX2NvdW50X21hdDwtY291bnRfbWF0DQpjb3VudF9tYXQgPC0gZHBseXI6OmNvdW50KHBEYXRhKERhdGEpLCBBTk4xLEFOTjIsQU5OMyxBTk40LHNsaWRlX25hbWUpDQoNCiMgc2ltcGxpZnlfc2xpZGVfbmFtZXMgaWYgbmVlZGVkDQojY291bnRfbWF0JHNsaWRlX25hbWUgPC0gZ3N1YigiZGlzZWFzZSIsICJkIiwgZ3N1Yigibm9ybWFsIiwgIm4iLCBjb3VudF9tYXQkc2xpZGVfbmFtZSkpDQoNCiMgZ2F0aGVyIHRoZSBkYXRhIGFuZCBwbG90IGluIG9yZGVyOiBjbGFzcywgc2xpZGUgbmFtZSwgcmVnaW9uLCBzZWdtZW50DQp0ZXN0X2dyIDwtIGdhdGhlcl9zZXRfZGF0YShjb3VudF9tYXQsIDE6YW5uX3NpemUpDQp0ZXN0X2dyJHggPC0gDQogIGZhY3Rvcih0ZXN0X2dyJHgsIA0KICAgICAgICAgbGV2ZWxzID0gYW5uX25hbWVzKQ0KDQoNCmFvaWxpc3QgPC1uYW1lcyhhcy5kYXRhLmZyYW1lKGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGEsIGVsdD0gImV4cHJzIikpKQ0KDQpBTk4xIDwtYXMuZGF0YS5mcmFtZShwRGF0YSh0YXJnZXRfRGF0YSkkQU5OMSx1bmlxdWUoY291bnRfbWF0JEFOTjEpKQ0KY29sbmFtZXMoQU5OMSkgPC0gImNsYXNzIg0Kcm93Lm5hbWVzKEFOTjEpIDwtYW9pbGlzdA0KDQpBTk4yIDwtYXMuZGF0YS5mcmFtZShwRGF0YSh0YXJnZXRfRGF0YSkkQU5OMix1bmlxdWUoY291bnRfbWF0JEFOTjIpKQ0KY29sbmFtZXMoQU5OMikgPC0gInNsaWRlX2FubiINCnJvdy5uYW1lcyhBTk4yKSA8LWFvaWxpc3QNCg0KQU5OMyA8LWFzLmRhdGEuZnJhbWUocERhdGEodGFyZ2V0X0RhdGEpJEFOTjMsdW5pcXVlKGNvdW50X21hdCRBTk4zKSkNCmNvbG5hbWVzKEFOTjMpIDwtICJyZWdpb24iDQpyb3cubmFtZXMoQU5OMykgPC1hb2lsaXN0DQoNCkFOTjQgPC1hcy5kYXRhLmZyYW1lKHBEYXRhKHRhcmdldF9EYXRhKSRBTk40LHVuaXF1ZShjb3VudF9tYXQkQU5ONCkpDQpjb2xuYW1lcyhBTk40KSA8LSAicGF0aCINCnJvdy5uYW1lcyhBTk40KSA8LWFvaWxpc3QNCg0KU04gPC1hcy5kYXRhLmZyYW1lKHBEYXRhKHRhcmdldF9EYXRhKSRzbGlkZV9uYW1lLCB1bmlxdWUoY291bnRfbWF0JHNsaWRlX25hbWUpKQ0KY29sbmFtZXMoU04pIDwtICJzbGlkZV9uYW1lIg0Kcm93Lm5hbWVzKFNOKSA8LWFvaWxpc3QNCg0KYW5uPC1jYmluZChBTk4xLEFOTjIsQU5OMyxBTk40LFNOKQ0KYGBgDQoNCiMgNC42IE1hbnVhbCByZW1vdmFsIG9mIHNhbXBsZXMvY2xhc3Nlcw0KDQpgYGB7ciByZW1vdmVfc2FtcGxlc30NCmFjdGl2ZV9hb2lzPC1yb3duYW1lcyhhbm4pDQpgYGANCg0KcmUtQ29sbGVjdCBhbm5vdGF0aW9ucw0KDQpgYGB7ciBjb2xsZWN0X2Fubm90YXRpb25zfQ0KIyBnYXRoZXIgdGhlIGRhdGEgYW5kIHBsb3QgaW4gb3JkZXI6IGNsYXNzLCBzbGlkZSBuYW1lLCByZWdpb24sIHNlZ21lbnQNCnRlc3RfZ3IgPC0gZ2F0aGVyX3NldF9kYXRhKGNvdW50X21hdCwgMTphbm5fc2l6ZSkNCnRlc3RfZ3IkeCA8LQ0KICBmYWN0b3IodGVzdF9nciR4LA0KICAgICAgICAgbGV2ZWxzID0gYW5uX25hbWVzKQ0KDQpgYGANCg0KcmUtUGxvdCBTYW5rZXkNCg0KYGBge3IgcGxvdF9zYW5rZXksIGZpZy53aWR0aD0yMCxmaWcuaGVpZ2h0PTExfQ0KZ2dwbG90KHRlc3RfZ3IsIGFlcyh4LCBpZCA9IGlkLCBzcGxpdCA9IHksIHZhbHVlID0gbikpICsNCiAgZ2VvbV9wYXJhbGxlbF9zZXRzKGFlcyhmaWxsID0gQU5OMSksIGFscGhhID0gMC41LCBheGlzLndpZHRoID0gMC4xKSArDQogIGdlb21fcGFyYWxsZWxfc2V0c19heGVzKGF4aXMud2lkdGggPSAwLjIpICsNCiAgZ2VvbV9wYXJhbGxlbF9zZXRzX2xhYmVscyhjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSA1KSArDQogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTcpICsgDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbigwKSkgKyANCiAgc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQgPSBleHBhbnNpb24oMCkpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNvbG9yX2xpc3QkQU5OMSkgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiKSArDQogIGFubm90YXRlKGdlb20gPSAic2VnbWVudCIsIHggPSAzLjI1LCB4ZW5kID0gMy4yNSwgeSA9IDEwLCANCiAgICAgICAgICAgeWVuZCA9IDYwLCBsd2QgPSAyKSArDQogIGFubm90YXRlKGdlb20gPSAidGV4dCIsIHggPSAzLjE5LCB5ID0gMjUsIGFuZ2xlID0gOTAsIHNpemUgPSA1LA0KICAgICAgICAgICBoanVzdCA9IDAuNSwgbGFiZWwgPSAiNTAgc2VnbWVudHMiKQ0KYGBgDQoNCiMgNC43IEdlbmUgRGV0ZWN0aW9uIFJhdGUNCg0KQ2FsY3VsYXRlIGRldGVjdGlvbiByYXRlDQoNCmBgYHtyIGNsY19kZXRlY3Rpb25fcmF0ZX0NCkxPUV9NYXQgPC0gTE9RX01hdFssIGNvbG5hbWVzKHRhcmdldF9EYXRhKV0NCmZEYXRhKHRhcmdldF9EYXRhKSREZXRlY3RlZFNlZ21lbnRzIDwtIHJvd1N1bXMoTE9RX01hdCwgbmEucm0gPSBUUlVFKQ0KZkRhdGEodGFyZ2V0X0RhdGEpJERldGVjdGlvblJhdGUgPC0NCiAgZkRhdGEodGFyZ2V0X0RhdGEpJERldGVjdGVkU2VnbWVudHMgLyBucm93KHBEYXRhKHRhcmdldF9EYXRhKSkNCmBgYA0KDQpHZW5lIG9mIGludGVyZXN0IGRldGVjdGlvbiB0YWJsZQ0KDQpgYGB7ciBnZW5lX29mX2ludGVyZXN0X3RhYmxlfQ0KZ29pIDwtIGMoIlBEQ0QxIiwgIkNEMjc0IiwgIklGTkciLCAiQ0Q4QSIsICJDRDY4IiwgIkVQQ0FNIiwNCiAgICAgICAgICJLUlQxOCIsICJOUEhTMSIsICJOUEhTMiIsICJDQUxCMSIsICJDTEROOCIpDQpnb2lfZGYgPC0gZGF0YS5mcmFtZSgNCiAgR2VuZSA9IGdvaSwNCiAgTnVtYmVyID0gZkRhdGEodGFyZ2V0X0RhdGEpW2dvaSwgIkRldGVjdGVkU2VnbWVudHMiXSwNCiAgRGV0ZWN0aW9uUmF0ZSA9IHBlcmNlbnQoZkRhdGEodGFyZ2V0X0RhdGEpW2dvaSwgIkRldGVjdGlvblJhdGUiXSkpDQpgYGANCg0KIyA0LjggR2VuZSBGaWx0ZXJpbmcNCg0KV2Ugd2lsbCBncmFwaCB0aGUgdG90YWwgbnVtYmVyIG9mIGdlbmVzIGRldGVjdGVkIGluIGRpZmZlcmVudA0KcGVyY2VudGFnZXMgb2Ygc2VnbWVudHMuIEJhc2VkIG9uIHRoZSB2aXN1YWxpemF0aW9uIGJlbG93LCB3ZSBjYW4gYmV0dGVyDQp1bmRlcnN0YW5kIGdsb2JhbCBnZW5lIGRldGVjdGlvbiBpbiBvdXIgc3R1ZHkgYW5kIHNlbGVjdCBob3cgbWFueSBsb3cNCmRldGVjdGVkIGdlbmVzIHRvIGZpbHRlciBvdXQgb2YgdGhlIGRhdGFzZXQuIEdlbmUgZmlsdGVyaW5nIGluY3JlYXNlcw0KcGVyZm9ybWFuY2Ugb2YgZG93bnN0cmVhbSBzdGF0aXN0aWNhbCB0ZXN0cyBhbmQgaW1wcm92ZXMgaW50ZXJwcmV0YXRpb24NCm9mIHRydWUgYmlvbG9naWNhbCBzaWduYWwuDQoNClBsb3QgZGV0ZWN0aW9uIHJhdGUNCg0KYGBge3IgcGxvdF9kZXRfcmF0ZX0NCnBsb3RfZGV0ZWN0IDwtIGRhdGEuZnJhbWUoRnJlcSA9IGMoMSwgNSwgMTAsIDIwLCAzMCwgNTApKQ0KcGxvdF9kZXRlY3QkTnVtYmVyIDwtDQogIHVubGlzdChsYXBwbHkoYygwLjAxLCAwLjA1LCAwLjEsIDAuMiwgMC4zLCAwLjUpLA0KICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIHtzdW0oZkRhdGEodGFyZ2V0X0RhdGEpJERldGVjdGlvblJhdGUgPj0geCl9KSkNCnBsb3RfZGV0ZWN0JFJhdGUgPC0gcGxvdF9kZXRlY3QkTnVtYmVyIC8gbnJvdyhmRGF0YSh0YXJnZXRfRGF0YSkpDQpyb3duYW1lcyhwbG90X2RldGVjdCkgPC0gcGxvdF9kZXRlY3QkRnJlcQ0KDQpnZ3Bsb3QocGxvdF9kZXRlY3QsIGFlcyh4ID0gYXMuZmFjdG9yKEZyZXEpLCB5ID0gUmF0ZSwgZmlsbCA9IFJhdGUpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBmb3JtYXRDKE51bWJlciwgZm9ybWF0ID0gImQiLCBiaWcubWFyayA9ICIsIikpLA0KICAgICAgICAgICAgdmp1c3QgPSAxLjYsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDQpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gIm9yYW5nZTIiLCBtaWQgPSAibGlnaHRibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgaGlnaCA9ICJkb2RnZXJibHVlMyIsIG1pZHBvaW50ID0gMC42NSwNCiAgICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLDEpLA0KICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsNCiAgdGhlbWVfYncoKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsIGxpbWl0cyA9IGMoMCwxKSwNCiAgICAgICAgICAgICAgICAgICAgIGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwKSkpICsNCiAgbGFicyh4ID0gIiUgb2YgU2VnbWVudHMiLA0KICAgICAgIHkgPSAiR2VuZXMgRGV0ZWN0ZWQsICUgb2YgUGFuZWwgPiBMT1EiKQ0KYGBgDQoNClN1YnNldCB0byB0YXJnZXQgZ2VuZXMgZGV0ZWN0ZWQgaW4gYXQgbGVhc3QgMTAlIG9mIHRoZSBzYW1wbGVzLiBBbHNvDQptYW51YWxseSBpbmNsdWRlIHRoZSBuZWdhdGl2ZSBjb250cm9sIHByb2JlLCBmb3IgZG93bnN0cmVhbSB1c2UNCg0KYGBge3Igc3Vic2V0X3RvXzEwcF9kZXRlY3RlZF9nZW5lc30NCiMgZGVmYXVsdD0wLjENCm5lZ2F0aXZlUHJvYmVmRGF0YSA8LSBzdWJzZXQoZkRhdGEodGFyZ2V0X0RhdGEpLCBDb2RlQ2xhc3MgPT0gIk5lZ2F0aXZlIikNCm5lZ19wcm9iZXMgPC0gdW5pcXVlKG5lZ2F0aXZlUHJvYmVmRGF0YSRUYXJnZXROYW1lKQ0KdGFyZ2V0X0RhdGEgPC0gDQogIHRhcmdldF9EYXRhW2ZEYXRhKHRhcmdldF9EYXRhKSREZXRlY3Rpb25SYXRlID49IDAuMDUgfA0KICAgICAgICAgICAgICAgICAgICBmRGF0YSh0YXJnZXRfRGF0YSkkVGFyZ2V0TmFtZSAlaW4lIG5lZ19wcm9iZXMsIF0NCg0KIyByZXRhaW4gb25seSBkZXRlY3RlZCBnZW5lcyBvZiBpbnRlcmVzdA0KZ29pIDwtIGdvaVtnb2kgJWluJSByb3duYW1lcyh0YXJnZXRfRGF0YSldDQpgYGANCg0KIyA1IE5vcm1hbGl6YXRpb24NCg0KV2Ugd2lsbCBub3cgbm9ybWFsaXplIHRoZSBHZW9NeCBkYXRhIGZvciBkb3duc3RyZWFtIHZpc3VhbGl6YXRpb25zIGFuZA0KZGlmZmVyZW50aWFsIGV4cHJlc3Npb24uIFRoZSB0d28gY29tbW9uIG1ldGhvZHMgZm9yIG5vcm1hbGl6YXRpb24gb2YNCkRTUC1OR1MgUk5BIGRhdGEgYXJlIGkpIHF1YXJ0aWxlIDMgKFEzKSBvciBpaSkgYmFja2dyb3VuZCBub3JtYWxpemF0aW9uLg0KDQpCb3RoIG9mIHRoZXNlIG5vcm1hbGl6YXRpb24gbWV0aG9kcyBlc3RpbWF0ZSBhIG5vcm1hbGl6YXRpb24gZmFjdG9yIHBlcg0Kc2VnbWVudCB0byBicmluZyB0aGUgc2VnbWVudCBkYXRhIGRpc3RyaWJ1dGlvbnMgdG9nZXRoZXIuIE1vcmUgYWR2YW5jZWQNCm1ldGhvZHMgZm9yIG5vcm1hbGl6YXRpb24gYW5kIG1vZGVsaW5nIGFyZSB1bmRlciBhY3RpdmUgZGV2ZWxvcG1lbnQuDQpIb3dldmVyLCBmb3IgbW9zdCBzdHVkaWVzLCB0aGVzZSBtZXRob2RzIGFyZSBzdWZmaWNpZW50IGZvcg0KdW5kZXJzdGFuZGluZyBkaWZmZXJlbmNlcyBiZXR3ZWVuIGJpb2xvZ2ljYWwgY2xhc3NlcyBvZiBzZWdtZW50cyBhbmQNCnNhbXBsZXMuDQoNClEzIG5vcm1hbGl6YXRpb24gaXMgdHlwaWNhbGx5IHRoZSBwcmVmZXJyZWQgbm9ybWFsaXphdGlvbiBzdHJhdGVneSBmb3INCm1vc3QgRFNQLU5HUyBSTkEgc3R1ZGllcy4gR2l2ZW4gdGhlIGxvdyBuZWdhdGl2ZSBwcm9iZSBjb3VudHMgaW4gdGhpcw0KcGFydGljdWxhciBkYXRhc2V0IGFzIHNob3duIGR1cmluZyBTZWdtZW50IFFDLCB3ZSB3b3VsZCBmdXJ0aGVyIGF2b2lkDQpiYWNrZ3JvdW5kIG5vcm1hbGl6YXRpb24gYXMgaXQgbWF5IGJlIGxlc3Mgc3RhYmxlLg0KDQpCZWZvcmUgbm9ybWFsaXphdGlvbiwgd2Ugd2lsbCBleHBsb3JlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgdXBwZXINCnF1YXJ0aWxlIChRMykgb2YgdGhlIGNvdW50cyBpbiBlYWNoIHNlZ21lbnQgd2l0aCB0aGUgZ2VvbWV0cmljIG1lYW4gb2YNCnRoZSBuZWdhdGl2ZSBjb250cm9sIHByb2JlcyBpbiB0aGUgZGF0YS4gSWRlYWxseSwgdGhlcmUgc2hvdWxkIGJlIGENCnNlcGFyYXRpb24gYmV0d2VlbiB0aGVzZSB0d28gdmFsdWVzIHRvIGVuc3VyZSB3ZSBoYXZlIHN0YWJsZSBtZWFzdXJlIG9mDQpRMyBzaWduYWwuIElmIHlvdSBkbyBub3Qgc2VlIHN1ZmZpY2llbnQgc2VwYXJhdGlvbiBiZXR3ZWVuIHRoZXNlIHZhbHVlcywNCnlvdSBtYXkgY29uc2lkZXIgbW9yZSBhZ2dyZXNzaXZlIGZpbHRlcmluZyBvZiBsb3cgc2lnbmFsIHNlZ21lbnRzL2dlbmVzLg0KDQpHcmFwaCBRMyB2YWx1ZSB2cyBuZWdHZW9NZWFuIG9mIE5lZ2F0aXZlcw0KDQpgYGB7ciBscG90X3EzX25lZ0dlb01lYW59DQphbm5fb2ZfaW50ZXJlc3QgPC0gIkFOTjMiDQpTdGF0X2RhdGEgPC0gDQogIGRhdGEuZnJhbWUocm93Lm5hbWVzID0gY29sbmFtZXMoZXhwcnModGFyZ2V0X0RhdGEpKSwNCiAgICAgICAgICAgICBTZWdtZW50ID0gY29sbmFtZXMoZXhwcnModGFyZ2V0X0RhdGEpKSwNCiAgICAgICAgICAgICBBbm5vdGF0aW9uID0gcERhdGEodGFyZ2V0X0RhdGEpWywgYW5uX29mX2ludGVyZXN0XSwNCiAgICAgICAgICAgICBRMyA9IHVubGlzdChhcHBseShleHBycyh0YXJnZXRfRGF0YSksIDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVhbnRpbGUsIDAuNzUsIG5hLnJtID0gVFJVRSkpLA0KICAgICAgICAgICAgIE5lZ1Byb2JlID0gZXhwcnModGFyZ2V0X0RhdGEpW25lZ19wcm9iZXMsIF0pDQpTdGF0X2RhdGFfbSA8LSBtZWx0KFN0YXRfZGF0YSwgbWVhc3VyZS52YXJzID0gYygiUTMiLCAiTmVnUHJvYmUiKSwNCiAgICAgICAgICAgICAgICAgICAgdmFyaWFibGUubmFtZSA9ICJTdGF0aXN0aWMiLCB2YWx1ZS5uYW1lID0gIlZhbHVlIikNCg0KcGx0MSA8LSBnZ3Bsb3QoU3RhdF9kYXRhX20sDQogICAgICAgICAgICAgICBhZXMoeCA9IFZhbHVlLCBmaWxsID0gU3RhdGlzdGljKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNDApICsgdGhlbWVfYncoKSArDQogIHNjYWxlX3hfY29udGludW91cyh0cmFucyA9ICJsb2cyIikgKw0KICBmYWNldF93cmFwKH5Bbm5vdGF0aW9uLCBucm93ID0gMSkgKyANCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9IDMsIHR5cGUgPSAicXVhbCIpICsNCiAgbGFicyh4ID0gIkNvdW50cyIsIHkgPSAiU2VnbWVudHMsICMiKQ0KDQpwbHQyIDwtIGdncGxvdChTdGF0X2RhdGEsDQogICAgICAgICAgICAgICBhZXMoeCA9IE5lZ1Byb2JlLCB5ID0gUTMsIGNvbG9yID0gQW5ub3RhdGlvbikpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBsdHkgPSAiZGFzaGVkIiwgY29sb3IgPSAiZGFya2dyYXkiKSArDQogIGdlb21fcG9pbnQoKSArIGd1aWRlcyhjb2xvciA9ICJub25lIikgKyB0aGVtZV9idygpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zID0gImxvZzIiKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMiIpICsNCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKw0KICBsYWJzKHggPSAiTmVnYXRpdmUgUHJvYmUgR2VvTWVhbiwgQ291bnRzIiwgeSA9ICJRMyBWYWx1ZSwgQ291bnRzIikNCg0KcGx0MyA8LSBnZ3Bsb3QoU3RhdF9kYXRhLA0KICAgICAgICAgICAgICAgYWVzKHggPSBOZWdQcm9iZSwgeSA9IFEzIC8gTmVnUHJvYmUsIGNvbG9yID0gQW5ub3RhdGlvbikpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgbHR5ID0gImRhc2hlZCIsIGNvbG9yID0gImRhcmtncmF5IikgKw0KICBnZW9tX3BvaW50KCkgKyB0aGVtZV9idygpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zID0gImxvZzIiKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMiIpICsNCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKw0KICBsYWJzKHggPSAiTmVnYXRpdmUgUHJvYmUgR2VvTWVhbiwgQ291bnRzIiwgeSA9ICJRMy9OZWdQcm9iZSBWYWx1ZSwgQ291bnRzIikNCg0KYnRtX3JvdyA8LSBwbG90X2dyaWQocGx0MiwgcGx0MywgbnJvdyA9IDEsIGxhYmVscyA9IGMoIkIiLCAiIiksDQogICAgICAgICAgICAgICAgICAgICByZWxfd2lkdGhzID0gYygwLjQzLDAuNTcpKQ0KcGxvdF9ncmlkKHBsdDEsIGJ0bV9yb3csIG5jb2wgPSAxLCBsYWJlbHMgPSBjKCJBIiwgIiIpKQ0KDQpgYGANCg0KUTMgbm9ybSAoNzV0aCBwZXJjZW50aWxlKSBmb3IgV1RBL0NUQSB3aXRoIG9yIHdpdGhvdXQgY3VzdG9tIHNwaWtlLWlucw0KDQpgYGB7ciBxM19ub3JtfQ0KdGFyZ2V0X0RhdGEgPC0gbm9ybWFsaXplKHRhcmdldF9EYXRhICwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm9ybV9tZXRob2QgPSAicXVhbnQiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVzaXJlZFF1YW50aWxlID0gLjc1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b0VsdCA9ICJxX25vcm0iKQ0KIywgZGF0YV90eXBlID0gIlJOQSIgZGVwcmljYXRlZCBhZnRlciA0LjENCg0KYGBgDQoNCkJhY2tncm91bmQgbm9ybWFsaXphdGlvbiBmb3IgV1RBL0NUQSB3aXRob3V0IGN1c3RvbSBzcGlrZS1pbg0KDQpgYGB7ciBiZ19ub3JtfQ0KdGFyZ2V0X0RhdGEgPC0gbm9ybWFsaXplKHRhcmdldF9EYXRhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtX21ldGhvZCA9ICJuZWciLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbUVsdCA9ICJleHBycyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvRWx0ID0gIm5lZ19ub3JtIikNCg0KIyAsIGRhdGFfdHlwZSA9ICJSTkEiIGRlcHJpY2F0ZWQgYWZ0ZXIgNC4xDQpgYGANCg0KIyA1LjEgVmlzdWFsaXplIHRoZSBmaXJzdCAxMCBzZWdtZW50cyB3aXRoIGVhY2ggbm9ybWFsaXphdGlvbiBtZXRob2Qgey50YWJzZXQgLnRhYnNldC1waWxsc30NCg0KVXNlIHRoZSB0YWItbWVudSB0byBuYXZpZ2F0ZSENCg0KYGBge3IgdmlzdWxhaXplX25vcm1zfQ0KDQojRml4IHplcm8gdmFsdWVzLCB3aGljaCBnbyB0byAtaW5mIGluIGxvZyB0cmFuc2Zvcm0gaW4gc3RhbmRhcmQgYm94cGxvdA0KIyB0ZW1wIDwtYXMubWF0cml4KGV4cHJzKCh0YXJnZXRfRGF0YSlbLDE6MTBdKSkNCiMgbG9uZyA8LSBtZWx0KHRlbXApDQojIGNvbG5hbWVzKGxvbmcpIDwtIGMoImdlbmUiLCJzZWdtZW50IiwiY291bnQiKQ0KIyBnZ3Bsb3QobG9uZywgYWVzKHg9c2VnbWVudCx5PWNvdW50KSkgKw0KIyAgICAgZ2VvbV9ib3hwbG90KGZpbGw9IiM5RURBRTUiKSArDQojICAgICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9c2NhbGVzOjpwc2V1ZG9fbG9nX3RyYW5zKGJhc2UgPSAxMCkpICsNCiMgICAgIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPWMoMToxMCkpICsNCiMgICAgIGxhYnModGl0bGU9IlJhdyBjb3VudHMiLCB4PSJzZWdtZW50IiwgeSA9ICJDb3VudHMsIFJhdyIpDQojIA0KIyANCiMgdGVtcCA8LWFzLm1hdHJpeChhc3NheURhdGFFbGVtZW50KHRhcmdldF9EYXRhWywxOjEwXSwgZWx0ID0gInFfbm9ybSIpKQ0KIyBsb25nIDwtIG1lbHQodGVtcCkNCiMgY29sbmFtZXMobG9uZykgPC0gYygiZ2VuZSIsInNlZ21lbnQiLCJjb3VudCIpDQojIGdncGxvdChsb25nLCBhZXMoeD1zZWdtZW50LHk9Y291bnQpKSArDQojICAgICBnZW9tX2JveHBsb3QoZmlsbCA9ICIjMkNBMDJDIikgKw0KIyAgICAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zPXNjYWxlczo6cHNldWRvX2xvZ190cmFucyhiYXNlID0gMTApKSArDQojICAgICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscz1jKDE6MTApKSArDQojICAgICBsYWJzKHRpdGxlPSJRMyBOb3JtIENvdW50cyIsIHg9InNlZ21lbnQiLCB5ID0gIkNvdW50cywgUTMgTm9ybWFsaXplZCIpDQojIA0KIyANCiMgdGVtcCA8LWFzLm1hdHJpeChhc3NheURhdGFFbGVtZW50KHRhcmdldF9EYXRhWywxOjEwXSwgZWx0ID0gIm5lZ19ub3JtIikpDQojIGxvbmcgPC0gbWVsdCh0ZW1wKQ0KIyBjb2xuYW1lcyhsb25nKSA8LSBjKCJnZW5lIiwic2VnbWVudCIsImNvdW50IikNCiMgZ2dwbG90KGxvbmcsIGFlcyh4PXNlZ21lbnQseT1jb3VudCkpICsNCiMgICAgIGdlb21fYm94cGxvdChmaWxsID0gIiNGRjdGMEUiKSArDQojICAgICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9c2NhbGVzOjpwc2V1ZG9fbG9nX3RyYW5zKGJhc2UgPSAxMCkpICsNCiMgICAgIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPWMoMToxMCkpICsNCiMgICAgIGxhYnModGl0bGU9Ik5lZyBOb3JtIENvdW50cyIsIHg9InNlZ21lbnQiLCB5ID0gIkNvdW50cywgTmVnLiBOb3JtYWxpemVkIikNCmBgYA0KDQojIyByYXcgY291bnRzDQoNCmBgYHtyfQ0KYm94cGxvdChleHBycyh0YXJnZXRfRGF0YSlbLDE6OF0sDQogICAgICAgIGNvbCA9ICIjOUVEQUU1IiwgbWFpbiA9ICJSYXcgQ291bnRzIiwNCiAgICAgICAgbG9nID0gInkiLCBuYW1lcyA9IDE6OCwgeGxhYiA9ICJTZWdtZW50IiwNCiAgICAgICAgeWxhYiA9ICJDb3VudHMsIFJhdyIpDQpgYGANCg0KIyMgUTMgbm9ybWFsaXplZCB7LmFjdGl2ZX0NCg0KYGBge3J9DQpib3hwbG90KGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGFbLDE6OF0sIGVsdCA9ICJxX25vcm0iKSwNCiAgICAgICAgY29sID0gIiMyQ0EwMkMiLCBtYWluID0gIlEzIE5vcm0gQ291bnRzIiwNCiAgICAgICAgbG9nID0gInkiLCBuYW1lcyA9IDE6OCwgeGxhYiA9ICJTZWdtZW50IiwNCiAgICAgICAgeWxhYiA9ICJDb3VudHMsIFEzIE5vcm1hbGl6ZWQiKQ0KYGBgDQoNCiMjIE5lZ2F0aXZlIHByb2JlIG5vcm1hbGl6YXRpb24NCg0KYGBge3J9DQpib3hwbG90KGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGFbLDE6OF0sIGVsdCA9ICJuZWdfbm9ybSIpLA0KICAgICAgICBjb2wgPSAiI0ZGN0YwRSIsIG1haW4gPSAiTmVnIE5vcm0gQ291bnRzIiwNCiAgICAgICAgbG9nID0gInkiLCBuYW1lcyA9IDE6OCwgeGxhYiA9ICJTZWdtZW50IiwNCiAgICAgICAgeWxhYiA9ICJDb3VudHMsIE5lZy4gTm9ybWFsaXplZCIpDQoNCmBgYA0KDQojIDYgVW5zdXBlcnZpc2VkIEFuYWx5c2lzDQoNCiMgNi4xIFVNQVAgey50YWJzZXQgLnRhYnNldC1waWxsc30NCg0KVXNlIHRoZSB0YWItbWVudSB0byBuYXZpZ2F0ZSENCg0KIyMgMQ0KDQpgYGB7ciB1bWFwLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD04fQ0KZ2MoKQ0KY3VzdG9tX3VtYXAgPC0gdW1hcDo6dW1hcC5kZWZhdWx0cw0KY3VzdG9tX3VtYXAkcmFuZG9tX3N0YXRlIDwtIDQyDQojIHJ1biBVTUFQDQoNCnVtYXBfb3V0IDwtDQogIHVtYXAodChsb2cyKGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGEgLCBlbHQgPSAicV9ub3JtIikpKSwNCiAgICAgICBjb25maWcgPSBjdXN0b21fdW1hcCkNCnBEYXRhKHRhcmdldF9EYXRhKVssIGMoIlVNQVAxIiwgIlVNQVAyIildIDwtIHVtYXBfb3V0JGxheW91dFssIGMoMSwyKV0NCmdncGxvdChwRGF0YSh0YXJnZXRfRGF0YSksDQogICAgICAgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IEFOTjEsIHNoYXBlID0gc2xpZGVfbmFtZSkpICsNCg0KICBnZW9tX3BvaW50KHNpemUgPSAzKSArDQogICNnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPXJvdy5uYW1lcyhwRGF0YSh0YXJnZXRfRGF0YSkpKSwgc2l6ZT0yLG1heC5vdmVybGFwcyA9IDEwMCkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIyAyDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD04fQ0KdW1hcF9vdXQgPC0NCiAgdW1hcCh0KGxvZzIoYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YSAsIGVsdCA9ICJxX25vcm0iKSkpLA0KICAgICAgIGNvbmZpZyA9IGN1c3RvbV91bWFwKQ0KcERhdGEodGFyZ2V0X0RhdGEpWywgYygiVU1BUDEiLCAiVU1BUDIiKV0gPC0gdW1hcF9vdXQkbGF5b3V0WywgYygxLDIpXQ0KZ2dwbG90KHBEYXRhKHRhcmdldF9EYXRhKSwNCiAgICAgICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gQU5OMiwgc2hhcGUgPSBBTk4xKSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAzKSArDQogICNnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPXJvdy5uYW1lcyhwRGF0YSh0YXJnZXRfRGF0YSkpKSwgc2l6ZT0yLG1heC5vdmVybGFwcyA9IDEwMCkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIyAzDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD04fQ0KdW1hcF9vdXQgPC0NCiAgdW1hcCh0KGxvZzIoYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YSAsIGVsdCA9ICJxX25vcm0iKSkpLA0KICAgICAgIGNvbmZpZyA9IGN1c3RvbV91bWFwKQ0KcERhdGEodGFyZ2V0X0RhdGEpWywgYygiVU1BUDEiLCAiVU1BUDIiKV0gPC0gdW1hcF9vdXQkbGF5b3V0WywgYygxLDIpXQ0KZ2dwbG90KHBEYXRhKHRhcmdldF9EYXRhKSwNCiAgICAgICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gQU5OMywgc2hhcGUgPSBBTk4yKSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAzKSArDQogICNnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPXJvdy5uYW1lcyhwRGF0YSh0YXJnZXRfRGF0YSkpKSwgc2l6ZT0yLG1heC5vdmVybGFwcyA9IDEwMCkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIDYuMSBSdW4gdFNORSB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQpVc2UgdGhlIHRhYi1tZW51IHRvIG5hdmlnYXRlIQ0KDQpPbmUgY29tbW9uIGFwcHJvYWNoIHRvIHVuZGVyc3RhbmRpbmcgaGlnaC1wbGV4IGRhdGEgaXMgZGltZW5zaW9uDQpyZWR1Y3Rpb24uIFR3byBjb21tb24gbWV0aG9kcyBhcmUgVU1BUCBhbmQgdFNORSwgd2hpY2ggYXJlDQpub24tb3J0aG9nb25hbGx5IGNvbnN0cmFpbmVkIHByb2plY3Rpb25zIHRoYXQgY2x1c3RlciBzYW1wbGVzIGJhc2VkIG9uDQpvdmVyYWxsIGdlbmUgZXhwcmVzc2lvbi4gSW4gdGhpcyBzdHVkeSwgd2Ugc2VlIGJ5IGVpdGhlciBVTUFQIChmcm9tIHRoZQ0KdW1hcCBwYWNrYWdlKSBvciB0U05FIChmcm9tIHRoZSBSdHNuZSBwYWNrYWdlKQ0KDQojIyAxDQoNCmBgYHtyIHRTTkUsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTh9DQpzZXQuc2VlZCg0MikgIyBzZXQgdGhlIHNlZWQgZm9yIHRTTkUgYXMgd2VsbA0KDQp0c25lX291dCA8LQ0KICBSdHNuZSh0KGxvZzIoYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YSAsIGVsdCA9ICJxX25vcm0iKSkpLA0KICAgICAgICBwZXJwbGV4aXR5ID0gbmNvbCh0YXJnZXRfRGF0YSkqLjE1KQ0KcERhdGEodGFyZ2V0X0RhdGEpWywgYygidFNORTEiLCAidFNORTIiKV0gPC0gdHNuZV9vdXQkWVssIGMoMSwyKV0NCmdncGxvdChwRGF0YSh0YXJnZXRfRGF0YSksDQogICAgICAgYWVzKHggPSB0U05FMSwgeSA9IHRTTkUyLCBzaGFwZSA9IEFOTjMsIGNvbG9yID0gQU5OMSkpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMykgKw0KICBnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPXJvdy5uYW1lcyhwRGF0YSh0YXJnZXRfRGF0YSkpKSwgc2l6ZT0yLG1heC5vdmVybGFwcyA9IDEwMCkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIyAyDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD04fQ0KdHNuZV9vdXQgPC0NCiAgUnRzbmUodChsb2cyKGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGEgLCBlbHQgPSAicV9ub3JtIikpKSwNCiAgICAgICAgcGVycGxleGl0eSA9IG5jb2wodGFyZ2V0X0RhdGEpKi4xNSkNCnBEYXRhKHRhcmdldF9EYXRhKVssIGMoInRTTkUxIiwgInRTTkUyIildIDwtIHRzbmVfb3V0JFlbLCBjKDEsMildDQpnZ3Bsb3QocERhdGEodGFyZ2V0X0RhdGEpLA0KICAgICAgIGFlcyh4ID0gdFNORTEsIHkgPSB0U05FMiwgY29sb3IgPSBBTk4yLCBzaGFwZSA9IEFOTjEpKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDMpICsNCiAgI2dlb21fdGV4dF9yZXBlbChhZXMobGFiZWw9cm93Lm5hbWVzKHBEYXRhKHRhcmdldF9EYXRhKSkpLCBzaXplPTIsbWF4Lm92ZXJsYXBzID0gMTAwKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCiMjIDMNCg0KYGBge3IsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTh9DQp0c25lX291dCA8LQ0KICBSdHNuZSh0KGxvZzIoYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YSAsIGVsdCA9ICJxX25vcm0iKSkpLA0KICAgICAgICBwZXJwbGV4aXR5ID0gbmNvbCh0YXJnZXRfRGF0YSkqLjE1KQ0KcERhdGEodGFyZ2V0X0RhdGEpWywgYygidFNORTEiLCAidFNORTIiKV0gPC0gdHNuZV9vdXQkWVssIGMoMSwyKV0NCmdncGxvdChwRGF0YSh0YXJnZXRfRGF0YSksDQogICAgICAgYWVzKHggPSB0U05FMSwgeSA9IHRTTkUyLCBjb2xvciA9IEFOTjMsIHNoYXBlID0gQU5OMikpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMykgKw0KICAjZ2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbD1yb3cubmFtZXMocERhdGEodGFyZ2V0X0RhdGEpKSksIHNpemU9MixtYXgub3ZlcmxhcHMgPSAxMDApKw0KICB0aGVtZV9idygpDQpgYGANCg0KIyA2LjIgQ2x1c3RlcmluZyBoaWdoIENWIEdlbmVzIHsudGFic2V0IC50YWJzZXQtcGlsbHN9DQoNCkFub3RoZXIgYXBwcm9hY2ggdG8gZXhwbG9yZSB0aGUgZGF0YSBpcyB0byBjYWxjdWxhdGUgdGhlIGNvZWZmaWNpZW50IG9mDQp2YXJpYXRpb24gKCRDViQpIGZvciBlYWNoIGdlbmUgKCRnJCkgdXNpbmcgdGhlIGZvcm11bGENCiRDVl9nPVNEX2cvbWVhbl9nJC4gV2UgdGhlbiBpZGVudGlmeSBnZW5lcyB3aXRoIGhpZ2ggQ1ZzIHRoYXQgc2hvdWxkDQpoYXZlIGxhcmdlIGRpZmZlcmVuY2VzIGFjcm9zcyB0aGUgdmFyaW91cyBwcm9maWxlZCBzZWdtZW50cy4gVGhpcw0KdW5iaWFzZWQgYXBwcm9hY2ggY2FuIHJldmVhbCBoaWdobHkgdmFyaWFibGUgZ2VuZXMgYWNyb3NzIHRoZSBzdHVkeS4NCg0KV2UgcGxvdCB0aGUgcmVzdWx0cyB1c2luZyB1bnN1cGVydmlzZWQgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsDQpkaXNwbGF5ZWQgYXMgYSBoZWF0bWFwLg0KDQpgYGB7ciBjbHVzdGVyaW5nMX0NCiMgY3JlYXRlIGEgbG9nMiB0cmFuc2Zvcm0gb2YgdGhlIGRhdGEgZm9yIGFuYWx5c2lzDQphc3NheURhdGFFbGVtZW50KG9iamVjdCA9IHRhcmdldF9EYXRhLCBlbHQgPSAibG9nX3EiKSA8LQ0KICBhc3NheURhdGFBcHBseSh0YXJnZXRfRGF0YSwgMiwgRlVOID0gbG9nLCBiYXNlID0gMiwgZWx0ID0gInFfbm9ybSIpDQoNCiMgY3JlYXRlIENWIGZ1bmN0aW9uDQpjYWxjX0NWIDwtIGZ1bmN0aW9uKHgpIHtzZCh4KSAvIG1lYW4oeCl9DQpDVl9kYXQgPC0gYXNzYXlEYXRhQXBwbHkodGFyZ2V0X0RhdGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgZWx0ID0gImxvZ19xIiwgTUFSR0lOID0gMSwgY2FsY19DVikNCiMgc2hvdyB0aGUgaGlnaGVzdCBDRCBnZW5lcyBhbmQgdGhlaXIgQ1YgdmFsdWVzDQpzb3J0KENWX2RhdCwgZGVjcmVhc2luZyA9IFRSVUUpWzE6NV0NCmBgYA0KDQojIyBUYWJsZSBvZiBDViB2YWx1ZXMNCg0KYGBge3J9DQojIHNob3cgdGhlIGhpZ2hlc3QgQ0QgZ2VuZXMgYW5kIHRoZWlyIENWIHZhbHVlcw0KZGF0YXRhYmxlKGFzLmRhdGEuZnJhbWUoQ1ZfZGF0KSwNCiAgICAgICAgICBleHRlbnNpb25zID0gJ0J1dHRvbnMnLCBvcHRpb25zID0gbGlzdCAoDQogICAgICAgICAgICBvcmRlciA9IGxpc3QoMSwgJ2Rlc2MnKSwNCiAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgYnV0dG9ucyA9IGMoJ2NvcHknLCAnY3N2JywgJ2V4Y2VsJywgJ3BkZicsICdwcmludCcpDQogICAgICAgICAgKSwgY2FwdGlvbiA9ICJDViB2YWx1ZXMgb2YgZ2VuZXMiIA0KKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJDVl9kYXQiKSwgZGlnaXRzPTMpDQoNCmBgYA0KDQojIyBIZWF0bWFwIGdlbmVzIGluIHRoZSB0b3AgM3JkIG9mIHRoZSBDViB2YWx1ZXMgey5hY3RpdmV9DQoNCmBgYHtyIENWaGVhdG1hcCwgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MTV9DQpHT0kgPC0gbmFtZXMoQ1ZfZGF0KVtDVl9kYXQgPiBxdWFudGlsZShDVl9kYXQsIDAuNzUpXQ0KDQpwaGVhdG1hcChhc3NheURhdGFFbGVtZW50KHRhcmdldF9EYXRhW0dPSSwgXSwgZWx0ID0gImxvZ19xIiksDQogICAgICAgICBzY2FsZSA9ICJyb3ciLA0KICAgICAgICAgY3V0cmVlX2NvbHMgPSAzLA0KICAgICAgICAgY3V0cmVlX3Jvd3MgPSAzLA0KICAgICAgICAgc2hvd19yb3duYW1lcyA9IEZBTFNFLCBzaG93X2NvbG5hbWVzID0gVFJVRSwNCiAgICAgICAgIGJvcmRlcl9jb2xvciA9IE5BLA0KICAgICAgICAgZHJvcF9sZXZlbHMgPSBUUlVFLA0KICAgICAgICAgY2x1c3RlcmluZ19tZXRob2QgPSAiYXZlcmFnZSIsDQogICAgICAgICBjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3MgPSAiY29ycmVsYXRpb24iLA0KICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImNvcnJlbGF0aW9uIiwNCiAgICAgICAgIGJyZWFrcyA9IHNlcSgtMywgMywgMC4wNSksDQogICAgICAgICBjb2xvciA9IGNvbG9yUmFtcFBhbGV0dGUoYygicHVycGxlMyIsICJibGFjayIsICJ5ZWxsb3cyIikpKDEyMCksDQogICAgICAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGNvbG9yX2xpc3QsDQogICAgICAgICBhbm5vdGF0aW9uX2NvbCA9IHBEYXRhKHRhcmdldF9EYXRhKVssIGFubl9uYW1lc10pDQoNCmBgYA0KDQpgYGB7ciBsb2dfdHJhbnNmb3JtfQ0KYXNzYXlEYXRhRWxlbWVudChvYmplY3QgPSB0YXJnZXRfRGF0YSwgZWx0ID0gImxvZ19xIikgPC0gIGFzc2F5RGF0YUFwcGx5KHRhcmdldF9EYXRhLCAyLCBGVU4gPSBsb2csIGJhc2UgPSAyLCBlbHQgPSAicV9ub3JtIikNCmxvZ19xIDwtYXMuZGF0YS5mcmFtZShhc3NheURhdGFFbGVtZW50KHRhcmdldF9EYXRhLCBlbHQ9ICJsb2dfcSIpKQ0KYGBgDQoNCiMgNi4yLjAgQ3JlYXRlIHN1YnNldCBvZiBkYXRhDQoNCmBgYHtyIGRlZmluZV9hY3RpdmVfYW9pc30NCiMgZGV0ZXJtaW5lIEFPSXMgdG8gdXNlDQojYWN0aXZlX2FvaXM8LXJvd25hbWVzKGFubilbYW5uJHBhdGllbnQ9PSJwNCJdDQphY3RpdmVfYW9pczwtcm93bmFtZXMoYW5uKQ0KDQpgYGANCg0KIyA2LjIuMSBDbHVzdGVyaW5nIGhpZ2ggQ1YgZ2VuZXMgZm9yIHN1YnNldCB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQpDYWxjdWxhdGluZyBDViB2YWx1ZXMNCg0KYGBge3IgY2x1c3RlcmluZ19zdWJzZXQsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTE1fQ0KIyBjcmVhdGUgYSBsb2cyIHRyYW5zZm9ybSBvZiB0aGUgZGF0YSBmb3IgYW5hbHlzaXMNCmFzc2F5RGF0YUVsZW1lbnQob2JqZWN0ID0gdGFyZ2V0X0RhdGEsIGVsdCA9ICJsb2dfcSIpIDwtDQogIGFzc2F5RGF0YUFwcGx5KHRhcmdldF9EYXRhLCAyLCBGVU4gPSBsb2csIGJhc2UgPSAyLCBlbHQgPSAicV9ub3JtIikNCg0KIyBjcmVhdGUgQ1YgZnVuY3Rpb24NCmNhbGNfQ1YgPC0gZnVuY3Rpb24oeCkge3NkKHgpIC8gbWVhbih4KX0NCkNWX2RhdCA8LSBhc3NheURhdGFBcHBseSh0YXJnZXRfRGF0YVssYWN0aXZlX2FvaXNdLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGVsdCA9ICJsb2dfcSIsIE1BUkdJTiA9IDEsIGNhbGNfQ1YpDQoNCmBgYA0KDQojIyBUYWJsZSBvZiBDViB2YWx1ZXMNCg0KYGBge3J9DQojIHNob3cgdGhlIGhpZ2hlc3QgQ0QgZ2VuZXMgYW5kIHRoZWlyIENWIHZhbHVlcw0KZGF0YXRhYmxlKGFzLmRhdGEuZnJhbWUoQ1ZfZGF0KSwNCiAgICAgICAgICBleHRlbnNpb25zID0gJ0J1dHRvbnMnLCBvcHRpb25zID0gbGlzdCAoDQogICAgICAgICAgICBvcmRlciA9IGxpc3QoMSwgJ2Rlc2MnKSwNCiAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgYnV0dG9ucyA9IGMoJ2NvcHknLCAnY3N2JywgJ2V4Y2VsJywgJ3BkZicsICdwcmludCcpDQogICAgICAgICAgKSwgY2FwdGlvbiA9ICJDViB2YWx1ZXMgb2YgZ2VuZXMiIA0KKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJDVl9kYXQiKSwgZGlnaXRzPTMpDQoNCmBgYA0KDQojIyBIZWF0bWFwIG9uIG9mIHN1YnNldCwgZ2VuZXMgaW4gdGhlIHRvcCAzcmQgb2YgdGhlIENWIHZhbHVlcyB7LmFjdGl2ZX0NCg0KYGBge3IsIGZpZy53aWR0aD0yMCxmaWcuaGVpZ2h0PTE1fQ0KIyBJZGVudGlmeSBnZW5lcyBpbiB0aGUgdG9wIDNyZCBvZiB0aGUgQ1YgdmFsdWVzDQpHT0kgPC0gbmFtZXMoQ1ZfZGF0KVtDVl9kYXQgPiBxdWFudGlsZShDVl9kYXQsIDAuNzUpXQ0KcGhlYXRtYXAoYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YVtHT0ksYWN0aXZlX2FvaXMgXSwgZWx0ID0gImxvZ19xIiksDQogICAgICAgIHNjYWxlID0gInJvdyIsDQogICAgICAgIGZvbnRzaXplX3JvdyA9IDUsDQogICAgICAgIGN1dHJlZV9jb2xzID0gMywNCiAgICAgICAgY3V0cmVlX3Jvd3MgPSAzLA0KICAgICAgICBzaG93X3Jvd25hbWVzID0gRkFMU0UsIHNob3dfY29sbmFtZXMgPSBUUlVFLA0KICAgICAgICBib3JkZXJfY29sb3IgPSBOQSwNCiAgICAgICAgY2x1c3RlcmluZ19tZXRob2QgPSAiYXZlcmFnZSIsDQogICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2VfY29scyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgIGJyZWFrcyA9IHNlcSgtMywgMywgMC4wNSksDQogICAgICAgIGNvbG9yID0gY29sb3JSYW1wUGFsZXR0ZShjKCJwdXJwbGUzIiwgImJsYWNrIiwgInllbGxvdzIiKSkoMTIwKSwNCiAgICAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGNvbG9yX2xpc3QsDQogICAgICAgIGFubm90YXRpb25fY29sID0NCiAgICAgICAgICBwRGF0YSh0YXJnZXRfRGF0YSlbLCBhbm5fbmFtZXNdKQ0KYGBgDQoNCiMgNy4xIERpZmZlcmVudGlhbCBFeHByZXNzaW9uDQoNCiMjIHQtdGVzdA0KDQpgYGB7ciwgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MTAgfQ0KZ2MoKQ0KcGxvdHM8LWxpc3QoKQ0KdGFibGVzPC1saXN0KCkNCmxhYmVsczwtbGlzdCgpDQp0ZXN0PC0idHRlc3QiDQptdGM8LSJCWSINCiNvcHRpb25zOiAiaG9sbSIgICAgICAgImhvY2hiZXJnIiAgICJob21tZWwiICAgICAiYm9uZmVycm9uaSIgIkJIIiAgICAgICAgICJCWSIgICAgICAgICAiZmRyIiANCmNvdW50ZXI9MQ0KDQpjb21wc19kZjwtZGF0YS5mcmFtZShjb21wPScnLHZhbD0nJykNCmZvciAoY2xhc3MgaW4gYygiREtEIiwibm9ybWFsIikpIHsNCmZvciAoYWN0aXZlX2dyb3VwMSBpbiB1bmlxdWUoYW5uJHJlZ2lvbikpIHsNCiAgICBmb3IgKGFjdGl2ZV9ncm91cDIgaW4gdW5pcXVlKGFubiRyZWdpb24pKSB7DQogICAgIA0KICAgICAgI3N1cHJlc3MgcmVkdW5jYW50IGNvbXBhcmVzDQogICAgICBpZihhY3RpdmVfZ3JvdXAxPT1hY3RpdmVfZ3JvdXAyKSB7bmV4dH0NCiAgICAgIGNvbXA8LXBhc3RlKHNvcnQoYyhjbGFzcywgYWN0aXZlX2dyb3VwMSxhY3RpdmVfZ3JvdXAyKSksY29sbGFwc2UgPSAiXyIpDQogICAgICBpZihjb21wICVpbiUgY29tcHNfZGYkY29tcCkge25leHR9DQogICAgICB0ZW1wX2RmPC1kYXRhLmZyYW1lKGNvbXA9Y29tcCAsdmFsPTEpDQogICAgICBjb21wc19kZjwtcmJpbmQoY29tcHNfZGYsdGVtcF9kZikNCiAgICAgIA0KICAgICAgbGFiZWxzW1tjb3VudGVyXV08LXBhc3RlKGFjdGl2ZV9ncm91cDEsIiB2cyAiLCBhY3RpdmVfZ3JvdXAyKQ0KICAgICAgZ3JvdXAxPC1sb2dfcVsscm93bmFtZXMoYW5uKVthbm4kY2xhc3MgPT0gY2xhc3MgJiBhbm4kcmVnaW9uPT1hY3RpdmVfZ3JvdXAxXV0NCiAgICAgIGdyb3VwMjwtbG9nX3FbLHJvd25hbWVzKGFubilbYW5uJGNsYXNzID09IGNsYXNzICYgYW5uJHJlZ2lvbj09YWN0aXZlX2dyb3VwMl1dDQogICAgICANCiAgICAgICNydW4gdF90ZXN0cyAgDQogICAgICByZXN1bHRzPC1hcy5kYXRhLmZyYW1lICggYXBwbHkobG9nX3EsIDEsIGZ1bmN0aW9uKHgpIHQudGVzdCh4W2NvbG5hbWVzKGdyb3VwMSldLHhbY29sbmFtZXMoZ3JvdXAyKV0pJHAudmFsdWUpICkNCiAgICAgIGNvbG5hbWVzKHJlc3VsdHMpPC0icmF3X3BfdmFsdWUiDQogICAgICANCiAgICAgICNtdWx0aXBsZV90ZXN0aW5nX2NvcnJlY3Rpb24NCiAgICAgIGFkal9wX3ZhbHVlPC0gcC5hZGp1c3QocmVzdWx0cyRyYXdfcF92YWx1ZSxtZXRob2Q9bXRjKQ0KICAgICAgcmVzdWx0czwtY2JpbmQocmVzdWx0cyxhZGpfcF92YWx1ZSkNCiAgICAgIA0KICAgICAgI2NhbGNfZmRyDQogICAgICBGRFI8LSBwLmFkanVzdChyZXN1bHRzJHJhd19wX3ZhbHVlLG1ldGhvZD0iZmRyIikNCiAgICAgIHJlc3VsdHM8LWNiaW5kKHJlc3VsdHMsRkRSKQ0KICAgICAgDQogICAgICAjZm9sZF9jaGFuZ2VzDQogICAgICAjYXMgYmFzZSBkYXRhIGlzIGFscmVhZHkgbG9nIHRyYW5zZm9ybWVkLCBtZWFucyBuZWVkIHRvIGJlIHN1YnRyYWN0ZWQgdG8gZ2V0IEZDIGluIGxvZyBzcGFjZQ0KICAgICAgZmNoYW5nZXM8LWFzLmRhdGEuZnJhbWUoYXBwbHkobG9nX3EsIDEsIGZ1bmN0aW9uKHgpIChtZWFuKHhbY29sbmFtZXMoZ3JvdXAxKV0pIC0gbWVhbih4W2NvbG5hbWVzKGdyb3VwMildKSkpKQ0KICAgICAgY29sbmFtZXMoZmNoYW5nZXMpPC0iRkMiDQogICAgICAjcGFzdGUoIkZDIixhY3RpdmVfZ3JvdXAxLCIgLyAiLGFjdGl2ZV9ncm91cDIpDQogICAgICByZXN1bHRzPC1jYmluZChyZXN1bHRzLGZjaGFuZ2VzKQ0KICAgICAgDQogICAgICAjYWRkIGdlbmVuYW1lcw0KICAgICAgcmVzdWx0cyRHZW5lPC1yb3duYW1lcyhyZXN1bHRzKQ0KICAgICAgDQogICAgICAjc2V0IGNhdGVnb3JpZXMgYmFzZWQgb24gUC12YWx1ZSAmIEZEUiBmb3IgcGxvdHRpbmcNCiAgICAgIHJlc3VsdHMkQ29sb3IgPC0gIk5TIG9yIEZDIDwgMC41Ig0KICAgICAgcmVzdWx0cyRDb2xvcltyZXN1bHRzJGFkal9wX3ZhbHVlIDwgMC4wNV0gPC0gIlAgPCAwLjA1Ig0KICAgICAgcmVzdWx0cyRDb2xvcltyZXN1bHRzJEZEUiA8IDAuMDVdIDwtICJGRFIgPCAwLjA1Ig0KICAgICAgcmVzdWx0cyRDb2xvcltyZXN1bHRzJEZEUiA8IDAuMDAxXSA8LSAiRkRSIDwgMC4wMDEiDQogICAgICByZXN1bHRzJENvbG9yW2FicyhyZXN1bHRzJEZDKSA8IDFdIDwtICJOUyBvciBGQyA8IDEiDQogICAgICByZXN1bHRzJENvbG9yIDwtIGZhY3RvcihyZXN1bHRzJENvbG9yLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiTlMgb3IgRkMgPCAxIiwgIlAgPCAwLjA1IiwgIkZEUiA8IDAuMDUiLCAiRkRSIDwgMC4wMDEiKSkNCiAgICAgIA0KICAgICAgI3Z1bGNhbm9wbG90DQogICAgICANCiAgICAgICMgcGljayB0b3AgZ2VuZXMgZm9yIGVpdGhlciBzaWRlIG9mIHZvbGNhbm8gdG8gbGFiZWwNCiAgICAgICMgb3JkZXIgZ2VuZXMgZm9yIGNvbnZlbmllbmNlOg0KICAgICAgDQogICAgICByZXN1bHRzJGludmVydF9QIDwtICgtbG9nMTAocmVzdWx0cyRhZGpfcF92YWx1ZSkpICogc2lnbihyZXN1bHRzJEZDKQ0KICAgICAgdG9wX2cgPC0gYygpDQogICAgICB0b3BfZyA8LSBjKHRvcF9nLA0KICAgICAgICAgICAgICAgICByZXN1bHRzW2luZCwgJ0dlbmUnXVsNCiAgICAgICAgICAgICAgICAgICBvcmRlcihyZXN1bHRzW2luZCwgJ2ludmVydF9QJ10sIGRlY3JlYXNpbmcgPSBUUlVFKVsxOjE1XV0sDQogICAgICAgICAgICAgICAgIHJlc3VsdHNbaW5kLCAnR2VuZSddW29yZGVyKHJlc3VsdHNbaW5kLCAnaW52ZXJ0X1AnXSwgZGVjcmVhc2luZyA9IEZBTFNFKVsxOjE1XV0pDQogICAgICB0b3BfZzwtIHVuaXF1ZSh0b3BfZykNCiAgICAgIHJlc3VsdHMgPC0gcmVzdWx0c1ssIC0xKm5jb2wocmVzdWx0cyldICMgcmVtb3ZlIGludmVydF9QIGZyb20gbWF0cml4DQogICAgICANCiAgICAgICMgR3JhcGggcmVzdWx0cw0KICAgICAgcGxvdHNbW2NvdW50ZXJdXTwtIGdncGxvdChyZXN1bHRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IEZDLCB5ID0gLWxvZzEwKGFkal9wX3ZhbHVlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gQ29sb3IsIGxhYmVsID0gR2VuZSkpICsNCiAgICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygxLCAtMSksIGx0eSA9ICJkYXNoZWQiKSArDQogICAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IC1sb2cxMCgwLjA1KSwgbHR5ID0gImRhc2hlZCIpICsNCiAgICAgICAgZ2VvbV9wb2ludCgpICsNCiAgICAgICAgbGFicyh4ID0gcGFzdGUoIkVucmljaGVkIGluIiwgYWN0aXZlX2dyb3VwMiwiIDwtIGxvZzIoRkMpIC0+IEVucmljaGVkIGluIiwgYWN0aXZlX2dyb3VwMSksDQogICAgICAgICAgICAgeSA9ICJTaWduaWZpY2FuY2UsIC1sb2cxMChQKSIsDQogICAgICAgICAgICAgY29sb3IgPSAiU2lnbmlmaWNhbmNlIikgKw0KICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhgRkRSIDwgMC4wMDFgID0gImRvZGdlcmJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgRkRSIDwgMC4wNWAgPSAibGlnaHRibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYFAgPCAwLjA1YCA9ICJvcmFuZ2UyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYE5TIG9yIEZDIDwgMC41YCA9ICJncmF5IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBndWlkZSA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSA0KSkpICsNCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLDAuMDUpKSkgKw0KICAgICAgICBnZW9tX3RleHRfcmVwZWwoZGF0YSA9IHN1YnNldChyZXN1bHRzLCBGRFI8MC4wMDEgJiAoLTE+RkN8IEZDPjEpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHBvaW50LnBhZGRpbmcgPSAwLjE1LCBjb2xvciA9ICJibGFjayIsIHNpemU9My41LA0KICAgICAgICAgICAgICAgICAgICAgICAgbWluLnNlZ21lbnQubGVuZ3RoID0gLjEsIGJveC5wYWRkaW5nID0gLjIsIGx3ZCA9IDIsDQogICAgICAgICAgICAgICAgICAgICAgICBtYXgub3ZlcmxhcHMgPSA1MCkgKw0KICAgICAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAyMCkgKw0KICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKw0KICAgICAgICBnZ3RpdGxlKHBhc3RlKCJjbGFzczogIiwgY2xhc3MsIiAtICIsdGVzdCwgbXRjLCJtdWx0aXRlc3QgY29yciIpKQ0KICAgICAgDQogICAgICAjc3RvcmUgdGFibGVzIGZvciBkaXNwbGF5IGxhdGVyDQogICAgICB0YWJsZXNbW2NvdW50ZXJdXTwtcmVzdWx0cw0KICAgICAgDQogICAgICAgY291bnRlciA9IGNvdW50ZXIrMQ0KICAgICAgI2RhdGF0YWJsZShzdWJzZXQocmVzdWx0cywgR2VuZSAlaW4lIEdPSSksIHJvd25hbWVzPUZBTFNFLGNhcHRpb24gPSBwYXN0ZSgiREUgcmVzdWx0cyAiLCBhY3RpdmVfZ3JvdXAxLCIgdnMgIiwgYWN0aXZlX2dyb3VwMikpDQogICAgfQ0KICB9DQp9DQpncmlkLmFycmFuZ2UoZ3JvYnM9cGxvdHMsbmNvbD0yKQ0KYGBgDQoNCmBgYHtyIGR1bXBfdGFibGVzLHJlc3VsdHM9J2FzaXMnfQ0KI3N0cmFuZ2x5IGRvZXMgbm90IGFwcGVhciBpbiBodG1sIG91dHB1dD8/DQpmb3IgKGMgaW4gKDI6Y291bnRlci0xKSkgew0KICAjR2VuZSAlaW4lIEdPSQ0KICANCiAgcHJpbnQoZGF0YXRhYmxlKCBzdWJzZXQodGFibGVzW1tjXV0sIENvbG9yID09ICJGRFIgPCAwLjAwMSIgKSwgDQogICAgICAgICAgIHJvd25hbWVzPUZBTFNFLA0KICAgICAgICAgICBleHRlbnNpb25zID0gJ0J1dHRvbnMnLCBvcHRpb25zID0gbGlzdCAoDQogICAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JykNCiAgICAgICAgICAgICksDQogICAgICAgICAgIGNhcHRpb24gPSBwYXN0ZSgiREUgcmVzdWx0cyAiLCBsYWJlbHNbWzFdXSksZmlsdGVyPSd0b3AnKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJyYXdfcF92YWx1ZSIsImFkal9wX3ZhbHVlIiwiRkRSIiwiRkMiKSwgZGlnaXRzPTMpKQ0KICBjYXQoJ1xuXG48IS0tIC0tPlxuXG4nKQ0KfSAgICAgICAgICAgIA0KDQpgYGANCg0KIyA3LjIgREUgYW5hbHlzaXMgd2l0aCBMTU0NCg0KQSBjb21tb24gc3RhdGlzdGljYWwgYXBwcm9hY2ggaXMgdG8gdXNlIGEgbGluZWFyIG1peGVkLWVmZmVjdCBtb2RlbA0KKExNTSkuIFRoZSBMTU0gYWxsb3dzIHRoZSB1c2VyIHRvIGFjY291bnQgZm9yIHRoZSBzdWJzYW1wbGluZyBwZXINCnRpc3N1ZTsgaW4gb3RoZXIgd29yZHMsIHdlIGFkanVzdCBmb3IgdGhlIGZhY3QgdGhhdCB0aGUgbXVsdGlwbGUgcmVnaW9ucw0Kb2YgaW50ZXJlc3QgcGxhY2VkIHBlciB0aXNzdWUgc2VjdGlvbiBhcmUgbm90IGluZGVwZW5kZW50IG9ic2VydmF0aW9ucywNCmFzIGlzIHRoZSBhc3N1bXB0aW9uIHdpdGggb3RoZXIgdHJhZGl0aW9uYWwgc3RhdGlzdGljYWwgdGVzdHMuIFRoZQ0KZm9ybXVsYXRpb24gb2YgdGhlIExNTSBtb2RlbCBkZXBlbmRzIG9uIHRoZSBzY2llbnRpZmljIHF1ZXN0aW9uIGJlaW5nDQphc2tlZC4NCg0KT3ZlcmFsbCwgdGhlcmUgYXJlIHR3byBmbGF2b3JzIG9mIHRoZSBMTU0gbW9kZWwgd2hlbiB1c2VkIHdpdGggR2VvTXgNCmRhdGE6IGkpIHdpdGggYW5kIGlpKSB3aXRob3V0IHJhbmRvbSBzbG9wZS4NCg0KV2hlbiBjb21wYXJpbmcgZmVhdHVyZXMgdGhhdCBjby1leGlzdCBpbiBhIGdpdmVuIHRpc3N1ZSBzZWN0aW9uLCBhDQpyYW5kb20gc2xvcGUgaXMgaW5jbHVkZWQgaW4gdGhlIExNTSBtb2RlbC4gV2hlbiBjb21wYXJpbmcgZmVhdHVyZXMgdGhhdA0KYXJlIG11dHVhbGx5IGV4Y2x1c2l2ZSBpbiBhIGdpdmVuIHRpc3N1ZSBzZWN0aW9uIHRoZSBMTU0gbW9kZWwgZG9lcyBub3QNCnJlcXVpcmUgYSByYW5kb20gc2xvcGUuDQoNCk1vc3RseSwgd2UgdXNlIHBhdGllbnQvc2FtcGxlIGFzIFJhbmRvbSBJbnRlcmNlcHQgd2hlbiB0aGV5IGFyZSBjb21iaW5lZA0Kb24gc2xpZGVzLg0KDQohW10oQzovVXNlcnMvcGtsb29zdGVybWFuL0RvY3VtZW50cy9nZW5lcmFsX3dvcmtmbG93L0xNTV9zZXR1cC5wbmcpDQoNCiMjIGdsb21lcnVsdXMgLSB0dWJ1bGUNCg0KYGBge3IgTE1NIHJlZ2lvbn0NCiMgY29udmVydCB0ZXN0IHZhcmlhYmxlcyB0byBmYWN0b3JzDQoNCnBEYXRhKHRhcmdldF9EYXRhKSR0ZXN0UmVnaW9uIDwtIA0KICAgIGZhY3RvcihwRGF0YSh0YXJnZXRfRGF0YSkkcmVnaW9uLCBjKCJnbG9tZXJ1bHVzIiwgInR1YnVsZSIpKQ0KDQpwRGF0YSh0YXJnZXRfRGF0YSlbWyJzbGlkZSJdXTwtZmFjdG9yKHBEYXRhKHRhcmdldF9EYXRhKVtbInNsaWRlX25hbWUiXV0pDQphc3NheURhdGFFbGVtZW50KG9iamVjdCA9IHRhcmdldF9EYXRhLCBlbHQgPSAibG9nX3EiKSA8LSAgYXNzYXlEYXRhQXBwbHkodGFyZ2V0X0RhdGEsIDIsIEZVTiA9IGxvZywgYmFzZSA9IDIsIGVsdCA9ICJxX25vcm0iKQ0KDQpsbW1fcmVzdWx0cyA8LSBjKCkNCm1peGVkT3V0bWMgPC0NCiAgICBtaXhlZE1vZGVsREUodGFyZ2V0X0RhdGEsDQogICAgICAgICAgICAgICAgIGVsdCA9ICJsb2dfcSIsDQogICAgICAgICAgICAgICAgIG1vZGVsRm9ybXVsYSA9IH4gdGVzdFJlZ2lvbiArICgxICsgdGVzdFJlZ2lvbiB8IHNsaWRlKSwNCiAgICAgICAgICAgICAgICAgZ3JvdXBWYXIgPSAidGVzdFJlZ2lvbiIsDQogICAgICAgICAgICAgICAgIG5Db3JlcyA9IHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpLA0KICAgICAgICAgICAgICAgICBtdWx0aUNvcmUgPSBGQUxTRSkNCg0KIyBmb3JtYXQgcmVzdWx0cyBhcyBkYXRhLmZyYW1lDQogcl90ZXN0IDwtIGRvLmNhbGwocmJpbmQsIG1peGVkT3V0bWNbImxzbWVhbnMiLCBdKQ0KIHRlc3RzIDwtIHJvd25hbWVzKHJfdGVzdCkNCiByX3Rlc3QgPC0gYXMuZGF0YS5mcmFtZShyX3Rlc3QpDQogcl90ZXN0JENvbnRyYXN0IDwtIHRlc3RzDQogDQojIHVzZSBsYXBwbHkgaW4gY2FzZSB5b3UgaGF2ZSBtdWx0aXBsZSBsZXZlbHMgb2YgeW91ciB0ZXN0IGZhY3RvciB0bw0KIyBjb3JyZWN0bHkgYXNzb2NpYXRlIGdlbmUgbmFtZSB3aXRoIGl0J3Mgcm93IGluIHRoZSByZXN1bHRzIHRhYmxlDQogcl90ZXN0JEdlbmUgPC0gDQogICAgIHVubGlzdChsYXBwbHkoY29sbmFtZXMobWl4ZWRPdXRtYyksDQogICAgICAgICAgICAgICAgICAgcmVwLCBucm93KG1peGVkT3V0bWNbImxzbWVhbnMiLCBdW1sxXV0pKSkNCg0KIHJfdGVzdCRGRFIgPC0gcC5hZGp1c3Qocl90ZXN0JGBQcig+fHR8KWAsIG1ldGhvZCA9ICJmZHIiKQ0KIHJfdGVzdCA8LSByX3Rlc3RbLCBjKCJHZW5lIiwgIkNvbnRyYXN0IiwgIkVzdGltYXRlIiwgIlByKD58dHwpIiwgIkZEUiIpXQ0KIGxtbV9yZXN1bHRzIDwtIHJiaW5kKGxtbV9yZXN1bHRzLCByX3Rlc3QpDQoNCmBgYA0KDQpgYGB7ciB0YWJsZV9vZl9MTU1fcmVzdWx0cyByZWdpb259DQojc3Vic2V0KGxtbV9yZXN1bHRzLCBHZW5lICVpbiUgR09JKQ0KZGF0YXRhYmxlKGxtbV9yZXN1bHRzLCByb3duYW1lcyA9IEZBTFNFLA0KICAgICAgICAgIGV4dGVuc2lvbnMgPSAnQnV0dG9ucycsIG9wdGlvbnMgPSBsaXN0ICgNCiAgICAgICAgICAgICAgZG9tID0gJ0JmdHJpcCcsDQogICAgICAgICAgICAgIGJ1dHRvbnMgPSBjKCdjb3B5JywgJ2NzdicsICdleGNlbCcsICdwZGYnLCAncHJpbnQnKQ0KICAgICAgICAgICAgKSwNCiAgICAgICAgICBjYXB0aW9uID0gIkRFIHJlc3VsdHMgZm9yIEdlbmVzIG9mIEludGVyZXN0ICg+NzUlIENWKSIsZmlsdGVyPSd0b3AnKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJFc3RpbWF0ZSIsIlByKD58dHwpIiwiRkRSIiksIGRpZ2l0cz0zKQ0KYGBgDQoNCiMjIERLRCAtIG5vcm1hbA0KDQpgYGB7ciBMTU0gY2xhc3N9DQojIGNvbnZlcnQgdGVzdCB2YXJpYWJsZXMgdG8gZmFjdG9ycw0KDQpwRGF0YSh0YXJnZXRfRGF0YSkkdGVzdENsYXNzIDwtIA0KICAgIGZhY3RvcihwRGF0YSh0YXJnZXRfRGF0YSkkY2xhc3MsIGMoIkRLRCIsICJub3JtYWwiKSkNCg0KcERhdGEodGFyZ2V0X0RhdGEpW1sic2xpZGUiXV08LWZhY3RvcihwRGF0YSh0YXJnZXRfRGF0YSlbWyJzbGlkZV9uYW1lIl1dKQ0KYXNzYXlEYXRhRWxlbWVudChvYmplY3QgPSB0YXJnZXRfRGF0YSwgZWx0ID0gImxvZ19xIikgPC0gYXNzYXlEYXRhQXBwbHkodGFyZ2V0X0RhdGEsIDIsIEZVTiA9IGxvZywgYmFzZSA9IDIsIGVsdCA9ICJxX25vcm0iKQ0KDQpsbW1fcmVzdWx0c19kIDwtIGMoKQ0KbWl4ZWRPdXRtYyA8LQ0KICAgIG1peGVkTW9kZWxERSh0YXJnZXRfRGF0YSwNCiAgICAgICAgICAgICAgICAgZWx0ID0gImxvZ19xIiwNCiAgICAgICAgICAgICAgICAgbW9kZWxGb3JtdWxhID0gfiB0ZXN0Q2xhc3MgKyAoMSB8IHNsaWRlKSwNCiAgICAgICAgICAgICAgICAgZ3JvdXBWYXIgPSAidGVzdENsYXNzIiwNCiAgICAgICAgICAgICAgICAgbkNvcmVzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCksDQogICAgICAgICAgICAgICAgIG11bHRpQ29yZSA9IEZBTFNFKQ0KDQojIGZvcm1hdCByZXN1bHRzIGFzIGRhdGEuZnJhbWUNCiByX3Rlc3QgPC0gZG8uY2FsbChyYmluZCwgbWl4ZWRPdXRtY1sibHNtZWFucyIsIF0pDQogdGVzdHMgPC0gcm93bmFtZXMocl90ZXN0KQ0KIHJfdGVzdCA8LSBhcy5kYXRhLmZyYW1lKHJfdGVzdCkNCiByX3Rlc3QkQ29udHJhc3QgPC0gdGVzdHMNCiANCiMgdXNlIGxhcHBseSBpbiBjYXNlIHlvdSBoYXZlIG11bHRpcGxlIGxldmVscyBvZiB5b3VyIHRlc3QgZmFjdG9yIHRvDQojIGNvcnJlY3RseSBhc3NvY2lhdGUgZ2VuZSBuYW1lIHdpdGggaXQncyByb3cgaW4gdGhlIHJlc3VsdHMgdGFibGUNCiByX3Rlc3QkR2VuZSA8LSANCiAgICAgdW5saXN0KGxhcHBseShjb2xuYW1lcyhtaXhlZE91dG1jKSwNCiAgICAgICAgICAgICAgICAgICByZXAsIG5yb3cobWl4ZWRPdXRtY1sibHNtZWFucyIsIF1bWzFdXSkpKQ0KDQogcl90ZXN0JEZEUiA8LSBwLmFkanVzdChyX3Rlc3QkYFByKD58dHwpYCwgbWV0aG9kID0gImZkciIpDQogcl90ZXN0IDwtIHJfdGVzdFssIGMoIkdlbmUiLCAiQ29udHJhc3QiLCAiRXN0aW1hdGUiLCAiUHIoPnx0fCkiLCAiRkRSIildDQogbG1tX3Jlc3VsdHNfZCA8LSByYmluZChsbW1fcmVzdWx0c19kLCByX3Rlc3QpDQoNCmBgYA0KDQpgYGB7ciB0YWJsZV9vZl9MTU1fcmVzdWx0cyBjbGFzc30NCiNzdWJzZXQobG1tX3Jlc3VsdHMsIEdlbmUgJWluJSBHT0kpDQpkYXRhdGFibGUobG1tX3Jlc3VsdHNfZCwgcm93bmFtZXMgPSBGQUxTRSwNCiAgICAgICAgICBleHRlbnNpb25zID0gJ0J1dHRvbnMnLCBvcHRpb25zID0gbGlzdCAoDQogICAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JykNCiAgICAgICAgICAgICksDQogICAgICAgICAgY2FwdGlvbiA9ICJERSByZXN1bHRzIGZvciBHZW5lcyBvZiBJbnRlcmVzdCAoPjc1JSBDVikiLGZpbHRlcj0ndG9wJykgJT4lIGZvcm1hdFJvdW5kKGNvbHVtbnM9YygiRXN0aW1hdGUiLCJQcig+fHR8KSIsIkZEUiIpLCBkaWdpdHM9MykNCmBgYA0KDQojIDcuMyBWdWxjYW5vcGxvdCBvZiBMTU0NCg0KIyMgZ2xvbWVydWx1cyAtIHR1YnVsZQ0KDQpgYGB7ciBsbW1fdnVsY2FubyByZWdpb24sIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTEwfQ0KIyBDYXRlZ29yaXplIFJlc3VsdHMgYmFzZWQgb24gUC12YWx1ZSAmIEZEUiBmb3IgcGxvdHRpbmcNCmZjX3RocmVzaG9sZCA9IDAuNQ0KDQpsbW1fcmVzdWx0cyRDb2xvciA8LSBwYXN0ZSgiTlMgb3IgRkMgPCAiLGZjX3RocmVzaG9sZCkNCmxtbV9yZXN1bHRzJENvbG9yW2xtbV9yZXN1bHRzJGBQcig+fHR8KWAgPCAwLjA1XSA8LSAiUCA8IDAuMDUiDQpsbW1fcmVzdWx0cyRDb2xvcltsbW1fcmVzdWx0cyRGRFIgPCAwLjA1XSA8LSAiRkRSIDwgMC4wNSINCmxtbV9yZXN1bHRzJENvbG9yW2xtbV9yZXN1bHRzJEZEUiA8IDAuMDAxXSA8LSAiRkRSIDwgMC4wMDEiDQpsbW1fcmVzdWx0cyRDb2xvclthYnMobG1tX3Jlc3VsdHMkRXN0aW1hdGUpIDwgZmNfdGhyZXNob2xkXSA8LSAiTlMgb3IgRkMgPCAxIg0KbG1tX3Jlc3VsdHMkQ29sb3IgPC0gZmFjdG9yKGxtbV9yZXN1bHRzJENvbG9yLA0KICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiTlMgb3IgRkMgPCAxIiwgIlAgPCAwLjA1IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZEUiA8IDAuMDUiLCAiRkRSIDwgMC4wMDEiKSkNCg0KDQoNCg0KIyBwaWNrIHRvcCBnZW5lcyBmb3IgZWl0aGVyIHNpZGUgb2Ygdm9sY2FubyB0byBsYWJlbA0KIyBvcmRlciBnZW5lcyBmb3IgY29udmVuaWVuY2U6DQpsbW1fcmVzdWx0cyRpbnZlcnRfUCA8LSAoLWxvZzEwKGxtbV9yZXN1bHRzJGBQcig+fHR8KWApKSAqIHNpZ24obG1tX3Jlc3VsdHMkRXN0aW1hdGUpDQp0b3BfZyA8LSBjKCkNCg0KI2xvb3AgaGVyZSBvdmVyIHRlc3RlZCBjb25kaXRpb25zIGlmIGFwcGxpY2FibGUNCiNmb3IobG9jYXRpb24gaW4gYygiQk9UVE9NIiwiVE9QIikpIHsNCnRvcF9nIDwtIGModG9wX2csDQogICAgICAgICAgIGxtbV9yZXN1bHRzWywgJ0dlbmUnXVsNCiAgICAgICAgICAgICBvcmRlcihsbW1fcmVzdWx0c1ssICdpbnZlcnRfUCddLCBkZWNyZWFzaW5nID0gVFJVRSlbMTozMF1dLA0KICAgICAgICAgICBsbW1fcmVzdWx0c1ssICdHZW5lJ11bDQogICAgICAgICAgICAgb3JkZXIobG1tX3Jlc3VsdHNbLCAnaW52ZXJ0X1AnXSwgZGVjcmVhc2luZyA9IEZBTFNFKVsxOjMwXV0pDQoNCnRvcF9nIDwtIHVuaXF1ZSh0b3BfZykNCmxtbV9yZXN1bHRzIDwtIGxtbV9yZXN1bHRzWywgLTEqbmNvbChsbW1fcmVzdWx0cyldICMgcmVtb3ZlIGludmVydF9QIGZyb20gbWF0cml4DQoNCg0KIyBnZXQgc2lnbmlmaWNhbnQgZ2VuZXMgd2l0aCBGRFIgPCAwLjAwMSBhbmQgZm9sZCBjaGFuZ2UgPiAwLjUNCiNsbW1fcmVzdWx0c19zbGljZSA8LSBsbW1fcmVzdWx0c19zbGljZVtsbW1fcmVzdWx0c19zbGljZSRGRFIgPCAwLjAwMSxdDQojbG1tX3Jlc3VsdHNfc2xpY2UgPC0gbG1tX3Jlc3VsdHNfc2xpY2VbbG1tX3Jlc3VsdHNfc2xpY2UkRXN0aW1hdGUgPCAtMC41IHwgbG1tX3Jlc3VsdHNfc2xpY2UkRXN0aW1hdGUgPiAwLjUsXQ0KDQoNCiMgR3JhcGggcmVzdWx0cw0KcHJpbnQoZ2dwbG90KGxtbV9yZXN1bHRzLA0KICAgICAgICAgICAgIGFlcyh4ID0gRXN0aW1hdGUsIHkgPSAtbG9nMTAoYFByKD58dHwpYCksDQogICAgICAgICAgICAgICAgIGNvbG9yID0gQ29sb3IsIGxhYmVsID0gR2VuZSkpICsNCiAgICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYyhmY190aHJlc2hvbGQsIC1mY190aHJlc2hvbGQpLCBsdHkgPSAiZGFzaGVkIikgKw0KICAgICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtbG9nMTAoMC4wNSksIGx0eSA9ICJkYXNoZWQiKSArDQogICAgICAgIGdlb21fcG9pbnQoKSArDQogICAgICAgIGxhYnMoeCA9IHBhc3RlKGxtbV9yZXN1bHRzJENvbnRyYXN0LCAiIGxvZzIoRkMpIiksDQogICAgICAgICAgICAgeSA9ICJTaWduaWZpY2FuY2UsIC1sb2cxMChQKSIsDQogICAgICAgICAgICAgY29sb3IgPSAiU2lnbmlmaWNhbmNlIikgKw0KICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhgRkRSIDwgMC4wMDFgID0gImRvZGdlcmJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgRkRSIDwgMC4wNWAgPSAibGlnaHRibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYFAgPCAwLjA1YCA9ICJvcmFuZ2UyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYE5TIG9yIEZDIDwgMWAgPSAiZ3JheSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gNCkpKSArDQogICAgICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMCwwLjA1KSkpICsNCiAgICAgICAgZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSBzdWJzZXQobG1tX3Jlc3VsdHMsIEZEUiA8IDAuMDAxICYgYWJzKGxtbV9yZXN1bHRzJEVzdGltYXRlKSA+IGZjX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICAgICAgICAgICBwb2ludC5wYWRkaW5nID0gMC4xNSwgY29sb3IgPSAiYmxhY2siLHNpemU9NSwNCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5zZWdtZW50Lmxlbmd0aCA9IC4xLCBib3gucGFkZGluZyA9IC4yLCBsd2QgPSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4Lm92ZXJsYXBzID0gNTApICsNCiAgICAgICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsNCiAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpKQ0KICAgICAgICAjK2ZhY2V0X3dyYXAoflN1YnNldCwgc2NhbGVzID0gImZyZWVfeSIpKQ0KDQpgYGANCg0KIyMgREtEIC0gbm9ybWFsDQoNCmBgYHtyIGxtbV92dWxjYW5vIGNsYXNzLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD0xMH0NCiMgQ2F0ZWdvcml6ZSBSZXN1bHRzIGJhc2VkIG9uIFAtdmFsdWUgJiBGRFIgZm9yIHBsb3R0aW5nDQpmY190aHJlc2hvbGQgPSAwLjUNCg0KbG1tX3Jlc3VsdHNfZCRDb2xvciA8LSBwYXN0ZSgiTlMgb3IgRkMgPCAiLGZjX3RocmVzaG9sZCkNCmxtbV9yZXN1bHRzX2QkQ29sb3JbbG1tX3Jlc3VsdHNfZCRgUHIoPnx0fClgIDwgMC4wNV0gPC0gIlAgPCAwLjA1Ig0KbG1tX3Jlc3VsdHNfZCRDb2xvcltsbW1fcmVzdWx0c19kJEZEUiA8IDAuMDVdIDwtICJGRFIgPCAwLjA1Ig0KbG1tX3Jlc3VsdHNfZCRDb2xvcltsbW1fcmVzdWx0c19kJEZEUiA8IDAuMDAxXSA8LSAiRkRSIDwgMC4wMDEiDQpsbW1fcmVzdWx0c19kJENvbG9yW2FicyhsbW1fcmVzdWx0c19kJEVzdGltYXRlKSA8IGZjX3RocmVzaG9sZF0gPC0gIk5TIG9yIEZDIDwgMSINCmxtbV9yZXN1bHRzX2QkQ29sb3IgPC0gZmFjdG9yKGxtbV9yZXN1bHRzX2QkQ29sb3IsDQogICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJOUyBvciBGQyA8IDEiLCAiUCA8IDAuMDUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRkRSIDwgMC4wNSIsICJGRFIgPCAwLjAwMSIpKQ0KDQoNCg0KDQojIHBpY2sgdG9wIGdlbmVzIGZvciBlaXRoZXIgc2lkZSBvZiB2b2xjYW5vIHRvIGxhYmVsDQojIG9yZGVyIGdlbmVzIGZvciBjb252ZW5pZW5jZToNCmxtbV9yZXN1bHRzX2QkaW52ZXJ0X1AgPC0gKC1sb2cxMChsbW1fcmVzdWx0c19kJGBQcig+fHR8KWApKSAqIHNpZ24obG1tX3Jlc3VsdHNfZCRFc3RpbWF0ZSkNCnRvcF9nIDwtIGMoKQ0KDQojbG9vcCBoZXJlIG92ZXIgdGVzdGVkIGNvbmRpdGlvbnMgaWYgYXBwbGljYWJsZQ0KI2Zvcihsb2NhdGlvbiBpbiBjKCJCT1RUT00iLCJUT1AiKSkgew0KdG9wX2cgPC0gYyh0b3BfZywNCiAgICAgICAgICAgbG1tX3Jlc3VsdHNfZFssICdHZW5lJ11bDQogICAgICAgICAgICAgb3JkZXIobG1tX3Jlc3VsdHNfZFssICdpbnZlcnRfUCddLCBkZWNyZWFzaW5nID0gVFJVRSlbMTozMF1dLA0KICAgICAgICAgICBsbW1fcmVzdWx0c19kWywgJ0dlbmUnXVsNCiAgICAgICAgICAgICBvcmRlcihsbW1fcmVzdWx0c19kWywgJ2ludmVydF9QJ10sIGRlY3JlYXNpbmcgPSBGQUxTRSlbMTozMF1dKQ0KDQp0b3BfZyA8LSB1bmlxdWUodG9wX2cpDQpsbW1fcmVzdWx0c19kIDwtIGxtbV9yZXN1bHRzX2RbLCAtMSpuY29sKGxtbV9yZXN1bHRzX2QpXSAjIHJlbW92ZSBpbnZlcnRfUCBmcm9tIG1hdHJpeA0KDQoNCiMgZ2V0IHNpZ25pZmljYW50IGdlbmVzIHdpdGggRkRSIDwgMC4wMDEgYW5kIGZvbGQgY2hhbmdlID4gMC41DQojbG1tX3Jlc3VsdHNfc2xpY2UgPC0gbG1tX3Jlc3VsdHNfc2xpY2VbbG1tX3Jlc3VsdHNfc2xpY2UkRkRSIDwgMC4wMDEsXQ0KI2xtbV9yZXN1bHRzX3NsaWNlIDwtIGxtbV9yZXN1bHRzX3NsaWNlW2xtbV9yZXN1bHRzX3NsaWNlJEVzdGltYXRlIDwgLTAuNSB8IGxtbV9yZXN1bHRzX3NsaWNlJEVzdGltYXRlID4gMC41LF0NCg0KIyBHcmFwaCByZXN1bHRzDQpwcmludChnZ3Bsb3QobG1tX3Jlc3VsdHNfZCwNCiAgICAgICAgICAgICBhZXMoeCA9IEVzdGltYXRlLCB5ID0gLWxvZzEwKGBQcig+fHR8KWApLA0KICAgICAgICAgICAgICAgICBjb2xvciA9IENvbG9yLCBsYWJlbCA9IEdlbmUpKSArDQogICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoZmNfdGhyZXNob2xkLCAtZmNfdGhyZXNob2xkKSwgbHR5ID0gImRhc2hlZCIpICsNCiAgICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gLWxvZzEwKDAuMDUpLCBsdHkgPSAiZGFzaGVkIikgKw0KICAgICAgICBnZW9tX3BvaW50KCkgKw0KICAgICAgICBsYWJzKHggPSBwYXN0ZShsbW1fcmVzdWx0c19kJENvbnRyYXN0LCAiIGxvZzIoRkMpIiksDQogICAgICAgICAgICAgeSA9ICJTaWduaWZpY2FuY2UsIC1sb2cxMChQKSIsDQogICAgICAgICAgICAgY29sb3IgPSAiU2lnbmlmaWNhbmNlIikgKw0KICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhgRkRSIDwgMC4wMDFgID0gImRvZGdlcmJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgRkRSIDwgMC4wNWAgPSAibGlnaHRibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYFAgPCAwLjA1YCA9ICJvcmFuZ2UyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYE5TIG9yIEZDIDwgMWAgPSAiZ3JheSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gNCkpKSArDQogICAgICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMCwwLjA1KSkpICsNCiAgICAgICAgZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSBzdWJzZXQobG1tX3Jlc3VsdHNfZCwgIEZEUiA8IDAuMDAxICYgYWJzKGxtbV9yZXN1bHRzX2QkRXN0aW1hdGUpID4gZmNfdGhyZXNob2xkKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHBvaW50LnBhZGRpbmcgPSAwLjE1LCBjb2xvciA9ICJibGFjayIsc2l6ZT01LA0KICAgICAgICAgICAgICAgICAgICAgICAgbWluLnNlZ21lbnQubGVuZ3RoID0gLjEsIGJveC5wYWRkaW5nID0gLjIsIGx3ZCA9IDIsDQogICAgICAgICAgICAgICAgICAgICAgICBtYXgub3ZlcmxhcHMgPSA1MCkgKw0KICAgICAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKw0KICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikpDQogICAgICAgICMrZmFjZXRfd3JhcCh+U3Vic2V0LCBzY2FsZXMgPSAiZnJlZV95IikpDQoNCmBgYA0KDQojIDcuNCBQbG90dGluZyBHZW5lcyBvZiBJbnRlcmVzdA0KDQpgYGB7ciBwbG90X2dlbmVfb2ZfaW50ZXJlc3QsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTV9DQpteV9nb2lzIDwtdW5pcXVlKHN1YnNldChsbW1fcmVzdWx0cywgYEZEUmAgPCAwLjAwMSkkR2VuZSkNCnRtcF90Ymw8LXN1YnNldChsbW1fcmVzdWx0cywgR2VuZSAlaW4lIG15X2dvaXMpDQoNCmlmKG5yb3codG1wX3RibCkgPiAxKSB7IA0KICBkYXRhdGFibGUodG1wX3RibCxyb3duYW1lcyA9IEZBTFNFLGNhcHRpb24gPSAiREUgcmVzdWx0cyBmb3IgR2VuZXMgb2YgSW50ZXJlc3QgIixmaWx0ZXI9J3RvcCcpICU+JSBmb3JtYXRSb3VuZChjb2x1bW5zPWMoIkVzdGltYXRlIiwiUHIoPnx0fCkiLCJGRFIiKSwgZGlnaXRzPTMpDQogDQpmb3IgKG15X2dvaSBpbiBteV9nb2lzKSB7DQojIHNob3cgZXhwcmVzc2lvbiBmb3IgYSBzaW5nbGUgdGFyZ2V0DQogIGE8LWdncGxvdChwRGF0YSh0YXJnZXRfRGF0YSksDQogICAgICAgYWVzKHggPSBBTk4xLCBmaWxsID0gQU5OMSwNCiAgICAgICAgICAgeSA9IGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGFbbXlfZ29pLCBdLCBlbHQgPSAicV9ub3JtIikpKSArDQogIGdlb21fdmlvbGluKCkgKw0KICBnZW9tX2ppdHRlcih3aWR0aCA9IC4yKSArDQogIGxhYnMoeSA9IHBhc3RlKG15X2dvaSwiRXhwcmVzc2lvbiIpKSArDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucyA9ICJsb2cyIikgKw0KICBmYWNldF93cmFwKH5BTk4zLCBucm93PTEpICsgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsNCiAgdGhlbWVfYncoKQ0KICBhDQp9DQp9ZWxzZXsNCiAgcHJpbnQoIk5vIHNpZ25pZmljYW50IGxNTSByZXN1bHRzIHRvIHBsb3QiKQ0KfQ0KYGBgDQoNCiMgNy41IEhlYXRtYXAgb2YgU2lnbmlmaWNhbnQgR2VuZXMNCg0KSW4gYWRkaXRpb24gdG8gZ2VuZXJhdGluZyBpbmRpdmlkdWFsIGdlbmUgYm94IHBsb3RzIG9yIHZvbGNhbm8gcGxvdHMsIHdlDQpjYW4gYWdhaW4gY3JlYXRlIGEgaGVhdG1hcCBmcm9tIG91ciBkYXRhLiBUaGlzIHRpbWUgcmF0aGVyIHRoYW4NCnV0aWxpemluZyBDViB0byBzZWxlY3QgZ2VuZXMsIHdlIGNhbiB1c2UgdGhlIFAtdmFsdWUgb3IgRkRSIHZhbHVlcyB0bw0Kc2VsZWN0IGdlbmVzLiBIZXJlLCB3ZSBwbG90IGFsbCBnZW5lcyB3aXRoIGFuIEZEUiBcPCAwLjAwMS4NCg0KYGBge3IgaGVhdG1hcF9zaWdfZ2VuZXMsIGZpZy53aWR0aD0yMCxmaWcuaGVpZ2h0PTIwfQ0KbXlfZ29pcyA8LXVuaXF1ZShzdWJzZXQobG1tX3Jlc3VsdHMsIGBGRFJgIDwgMC4wMDEpJEdlbmUpDQoNCmlmKGxlbmd0aChteV9nb2lzKT09MCkgew0KICBwcmludCgiTm8gc2lnbmlmaWNhbnQgcmVzdWx0cyB0byBzaG93IikNCiANCn1lbHNlew0KDQpwaGVhdG1hcChsb2cyKGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGFbbXlfZ29pcywgXSwgZWx0ID0gInFfbm9ybSIpKSwNCiAgICAgICAgIHNjYWxlID0gInJvdyIsDQogICAgICAgICBzaG93X3Jvd25hbWVzID0gVFJVRSwgc2hvd19jb2xuYW1lcyA9IFRSVUUsDQogICAgICAgICBib3JkZXJfY29sb3IgPSBOQSwNCiAgICAgICAgIGNsdXN0ZXJpbmdfbWV0aG9kID0gImF2ZXJhZ2UiLA0KICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzID0gImNvcnJlbGF0aW9uIiwNCiAgICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2VfY29scyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgICBjdXRyZWVfY29scyA9IDMsIGN1dHJlZV9yb3dzID0gMiwNCiAgICAgICAgIGJyZWFrcyA9IHNlcSgtMywgMywgMC4wNSksDQogICAgICAgICBjb2xvciA9IGNvbG9yUmFtcFBhbGV0dGUoYygicHVycGxlMyIsICJibGFjayIsICJ5ZWxsb3cyIikpKDEyMCksDQogICAgICAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGNvbG9yX2xpc3QsDQogICAgICAgICBhbm5vdGF0aW9uX2NvbCA9IHBEYXRhKHRhcmdldF9EYXRhKVssIGFubl9uYW1lc10pDQp9DQpgYGANCg0KIyA4IFBhdGh3YXkgQW5hbHlzaXMNCg0KUGF0aHdheSBhbmFseXNpcyBlbmFibGVzIGV4cGxvcmF0aW9uIG9mIGRpZmZlcmVudCBhZ2dyZWdhdGUgZ2VuZSBzZXRzDQpmb3Igb3VyIGV4cGVyaW1lbnRhbCBxdWVzdGlvbnMuIEVhY2ggaW5kaXZpZHVhbCBST0kvQU9JIHNlZ21lbnQgaXMNCnNjb3JlZCBmb3IgZXZlcnkgcGF0aHdheSBvZiBpbnRlcmVzdCwgd2hpY2ggd2UgY2FuIHRoZW4gdXNlIHRvDQppbnZlc3RpZ2F0ZSBiaW9sb2dpY2FsIGRpZmZlcmVuY2VzLiBXZSB3aWxsIHBlcmZvcm0gYW5hbG9nb3VzIGFuYWx5c2VzDQphcyB0aG9zZSBvdXRsaW5lZCBpbiB0aGUgRGlmZmVyZW50aWFsIEV4cHJlc3Npb24gYW5kIFNwYXRpYWwNCkRlY29udm9sdXRpb24gc2VjdGlvbnMgb2YgdGhlIHJlcG9ydCBmb3IgZ2VuZSBzZXQgZW5yaWNobWVudC4NCg0KIyA4LjEgU2NvcmluZyBHZW5lIFNldHMNCg0KUGF0aHdheXMgYW5kIGdlbmUgc2V0cyB3ZXJlIGRlZmluZWQgZnJvbSB0aGUgS2VnZyBCcml0ZSBkYXRhYmFzZS4gV2UgdXNlDQphbiBSIHNvZnR3YXJlIHBhY2thZ2UgY2FsbGVkIEdlbmUgU2V0IFZhcmlhdGlvbiBBbmFseXNpcyB0byBzY29yZSBlYWNoDQpzZWdtZW50IHdpdGhpbiBvdXIgc3R1ZHkuIHNlZQ0KPGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9tc2lnZGJyL3ZpZ25ldHRlcy9tc2lnZGJyLWludHJvLmh0bWw+DQpmb3Igb3B0aW9ucyBvbiBjb2xsZWN0aW9ucy4gV2UgdXNlIHRoZSBLRUdHIGFuZCBSRUFDVE9NRS4NCg0KYGBge3IgYnVpbGRfZ2VuZXNldHN9DQpoX2dlbmVfc2V0cyA9IHJiaW5kKG1zaWdkYnIoc3BlY2llcyA9ICJodW1hbiIsIHN1YmNhdGVnb3J5ID0gIkNQOktFR0ciKSwNCiAgICAgICAgICAgICAgICAgICAgbXNpZ2RicihzcGVjaWVzID0gImh1bWFuIiwgc3ViY2F0ZWdvcnkgPSAiQ1A6UkVBQ1RPTUUiKSkNCiNtc2lnZGJyKHNwZWNpZXMgPSAiaHVtYW4iLCBzdWJjYXRlZ29yeSA9ICJDUDpCSU9DQVJUQSIpDQoNCm1zaWdkYnJfbGlzdCA9IHNwbGl0KHggPSBoX2dlbmVfc2V0cyRnZW5lX3N5bWJvbCwgZiA9IGhfZ2VuZV9zZXRzJGdzX25hbWUpDQoNCiMgcHJlcGFyZSBkZiBmb3IgYWNjdXJhdGUgbWVyZ2luZyBiYWNrIGdlbmVzIGxhdGVyDQpwd19nZW5lX2RmPC1kYXRhLmZyYW1lKFBhdGh3YXkgPSBuYW1lcyhtc2lnZGJyX2xpc3QpKQ0KcHdfZ2VuZV9kZiRnZW5lczwtbXNpZ2Ricl9saXN0DQpgYGANCg0KYGBge3IgcnVuX2dzdmF9DQpzc2dzZWFfcmVzdWx0cyA8LSBHU1ZBOjpnc3ZhKGV4cHIgPSBhc3NheURhdGFFbGVtZW50KHRhcmdldF9EYXRhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsdCA9ICJsb2dfcSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdzZXQuaWR4Lmxpc3QgPSBtc2lnZGJyX2xpc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInpzY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluLnN6ID0gNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXguc3ogPSA1MDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFKQ0KZ2VuZVNldE9iaiA8LQ0KICBOYW5vU3RyaW5nR2VvTXhTZXQoYXNzYXlEYXRhID0gc3Nnc2VhX3Jlc3VsdHMsDQogICAgICAgICAgICAgICAgICAgICBwaGVub0RhdGEgPSBBbm5vdGF0ZWREYXRhRnJhbWUocERhdGEodGFyZ2V0X0RhdGEpKSwNCiAgICAgICAgICAgICAgICAgICAgIHByb3RvY29sRGF0YSA9IHByb3RvY29sRGF0YSh0YXJnZXRfRGF0YSksDQogICAgICAgICAgICAgICAgICAgICBmZWF0dXJlVHlwZSA9ICJHZW5lU2V0IiwNCiAgICAgICAgICAgICAgICAgICAgIGNoZWNrID0gRkFMU0UpDQpgYGANCg0KIyA4LjIgRGlmZmVyZW50YWwgYW5hbHlzaXMgb2YgcGF0aHdheXMNCg0KIyMgZ2xvbWVydWx1cyAtIHR1YnVsZQ0KDQpgYGB7ciBMTU1fb2ZfcGF0aHdheV9hbmFsaXNpcyByZWdpb259DQojICMgY29udmVydCB0ZXN0IHZhcmlhYmxlcyB0byBmYWN0b3JzDQpwRGF0YShnZW5lU2V0T2JqKVtbInNsaWRlIl1dPC1mYWN0b3IocERhdGEoZ2VuZVNldE9iailbWyJzbGlkZV9uYW1lIl1dKQ0KcERhdGEodGFyZ2V0X0RhdGEpJHRlc3RSZWdpb248LWZhY3RvcihwRGF0YSh0YXJnZXRfRGF0YSkkQU5OMywgdW5pcXVlKGNvdW50X21hdCRBTk4zKSkNCg0KbG1tX3NzZ3NlYV9yZXN1bHRzIDwtIGMoKQ0KDQptaXhlZE91dG1jIDwtDQogIG1peGVkTW9kZWxERShnZW5lU2V0T2JqLA0KICAgICAgICAgICAgICAgZWx0ID0gImV4cHJzIiwNCiAgICAgICAgICAgICAgIG1vZGVsRm9ybXVsYSA9IH4gdGVzdFJlZ2lvbiArICgxICsgdGVzdFJlZ2lvbiB8IHNsaWRlKSwNCiAgICAgICAgICAgICAgIGdyb3VwVmFyID0gInRlc3RSZWdpb24iLA0KICAgICAgICAgICAgICAgbkNvcmVzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCksDQogICAgICAgICAgICAgICBtdWx0aUNvcmUgPSBGQUxTRSkNCg0KIyBmb3JtYXQgcmVzdWx0cyBhcyBkYXRhLmZyYW1lDQpyX3NzZ3NlYV90ZXN0IDwtIGRvLmNhbGwocmJpbmQsIG1peGVkT3V0bWNbImxzbWVhbnMiLCBdKQ0Kc3Nnc2VhX3Rlc3RzIDwtIHJvd25hbWVzKHJfc3Nnc2VhX3Rlc3QpDQpyX3NzZ3NlYV90ZXN0IDwtIGFzLmRhdGEuZnJhbWUocl9zc2dzZWFfdGVzdCkNCnJfc3Nnc2VhX3Rlc3QkQ29udHJhc3QgPC0gc3Nnc2VhX3Rlc3RzDQojcl9zc2dzZWFfdGVzdCRHZW5lcyA8LSBtc2lnZGJyX2xpc3Qgc2VlbXMgdW5yZWxpYWJsZSBhcyBnc3ZhIG9taXRzIHBhdGh3YXlzIGlmIGdlbmVzIGFyZSBub3QgaW4gZGF0YS4uDQoNCiMgdXNlIGxhcHBseSBpbiBjYXNlIHlvdSBoYXZlIG11bHRpcGxlIGxldmVscyBvZiB5b3VyIHRlc3QgZmFjdG9yIHRvDQojIGNvcnJlY3RseSBhc3NvY2lhdGUgZ2VuZSBuYW1lIHdpdGggaXQncyByb3cgaW4gdGhlIHJlc3VsdHMgdGFibGUNCnJfc3Nnc2VhX3Rlc3QkUGF0aHdheSA8LQ0KICB1bmxpc3QobGFwcGx5KGNvbG5hbWVzKG1peGVkT3V0bWMpLA0KICAgICAgICAgICAgICAgIHJlcCwgbnJvdyhtaXhlZE91dG1jWyJsc21lYW5zIiwgXVtbMV1dKSkpDQoNCg0Kcl9zc2dzZWFfdGVzdCRGRFIgPC0gcC5hZGp1c3Qocl9zc2dzZWFfdGVzdCRgUHIoPnx0fClgLCBtZXRob2QgPSAiZmRyIikNCnJfc3Nnc2VhX3Rlc3QgPC0gcl9zc2dzZWFfdGVzdFssIGMoIlBhdGh3YXkiLCAiQ29udHJhc3QiLCAiRXN0aW1hdGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUHIoPnx0fCkiLCAiRkRSIildDQpsbW1fc3Nnc2VhX3Jlc3VsdHMgPC0gcmJpbmQobG1tX3NzZ3NlYV9yZXN1bHRzLCByX3NzZ3NlYV90ZXN0KQ0KbG1tX3NzZ3NlYV9yZXN1bHRzIDwtIG1lcmdlKGxtbV9zc2dzZWFfcmVzdWx0cywgcHdfZ2VuZV9kZikNCmBgYA0KDQojIyBES0QgLSBub3JtYWwNCg0KYGBge3IgTE1NX29mX3BhdGh3YXlfYW5hbGlzaXMgY2xhc3N9DQojICMgY29udmVydCB0ZXN0IHZhcmlhYmxlcyB0byBmYWN0b3JzDQpwRGF0YShnZW5lU2V0T2JqKVtbInNsaWRlIl1dPC1mYWN0b3IocERhdGEoZ2VuZVNldE9iailbWyJzbGlkZV9uYW1lIl1dKQ0KcERhdGEodGFyZ2V0X0RhdGEpJHRlc3RDbGFzczwtZmFjdG9yKHBEYXRhKHRhcmdldF9EYXRhKSRBTk4xLCB1bmlxdWUoY291bnRfbWF0JEFOTjEpKQ0KDQpsbW1fc3Nnc2VhX3Jlc3VsdHNfZCA8LSBjKCkNCg0KbWl4ZWRPdXRtYyA8LQ0KICBtaXhlZE1vZGVsREUoZ2VuZVNldE9iaiwNCiAgICAgICAgICAgICAgIGVsdCA9ICJleHBycyIsDQogICAgICAgICAgICAgICBtb2RlbEZvcm11bGEgPSB+IHRlc3RDbGFzcyArICgxIHwgc2xpZGUpLA0KICAgICAgICAgICAgICAgZ3JvdXBWYXIgPSAidGVzdENsYXNzIiwNCiAgICAgICAgICAgICAgIG5Db3JlcyA9IHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpLA0KICAgICAgICAgICAgICAgbXVsdGlDb3JlID0gRkFMU0UpDQoNCiMgZm9ybWF0IHJlc3VsdHMgYXMgZGF0YS5mcmFtZQ0Kcl9zc2dzZWFfdGVzdCA8LSBkby5jYWxsKHJiaW5kLCBtaXhlZE91dG1jWyJsc21lYW5zIiwgXSkNCnNzZ3NlYV90ZXN0cyA8LSByb3duYW1lcyhyX3NzZ3NlYV90ZXN0KQ0Kcl9zc2dzZWFfdGVzdCA8LSBhcy5kYXRhLmZyYW1lKHJfc3Nnc2VhX3Rlc3QpDQpyX3NzZ3NlYV90ZXN0JENvbnRyYXN0IDwtIHNzZ3NlYV90ZXN0cw0KI3Jfc3Nnc2VhX3Rlc3QkR2VuZXMgPC0gbXNpZ2Ricl9saXN0IHNlZW1zIHVucmVsaWFibGUgYXMgZ3N2YSBvbWl0cyBwYXRod2F5cyBpZiBnZW5lcyBhcmUgbm90IGluIGRhdGEuLg0KDQojIHVzZSBsYXBwbHkgaW4gY2FzZSB5b3UgaGF2ZSBtdWx0aXBsZSBsZXZlbHMgb2YgeW91ciB0ZXN0IGZhY3RvciB0bw0KIyBjb3JyZWN0bHkgYXNzb2NpYXRlIGdlbmUgbmFtZSB3aXRoIGl0J3Mgcm93IGluIHRoZSByZXN1bHRzIHRhYmxlDQpyX3NzZ3NlYV90ZXN0JFBhdGh3YXkgPC0NCiAgdW5saXN0KGxhcHBseShjb2xuYW1lcyhtaXhlZE91dG1jKSwNCiAgICAgICAgICAgICAgICByZXAsIG5yb3cobWl4ZWRPdXRtY1sibHNtZWFucyIsIF1bWzFdXSkpKQ0KDQoNCnJfc3Nnc2VhX3Rlc3QkRkRSIDwtIHAuYWRqdXN0KHJfc3Nnc2VhX3Rlc3QkYFByKD58dHwpYCwgbWV0aG9kID0gImZkciIpDQpyX3NzZ3NlYV90ZXN0IDwtIHJfc3Nnc2VhX3Rlc3RbLCBjKCJQYXRod2F5IiwgIkNvbnRyYXN0IiwgIkVzdGltYXRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlByKD58dHwpIiwgIkZEUiIpXQ0KbG1tX3NzZ3NlYV9yZXN1bHRzX2QgPC0gcmJpbmQobG1tX3NzZ3NlYV9yZXN1bHRzX2QsIHJfc3Nnc2VhX3Rlc3QpDQpsbW1fc3Nnc2VhX3Jlc3VsdHNfZCA8LSBtZXJnZShsbW1fc3Nnc2VhX3Jlc3VsdHNfZCwgcHdfZ2VuZV9kZikNCmBgYA0KDQojIDguMi4xIFRhYmxlIG9mIERpZmZlcmVudGFsIGFuYWx5c2lzIG9mIHBhdGh3YXlzDQoNCiMjIGdsb21lcnVsdXMgLSB0dWJ1bGUNCg0KYGBge3IgdGFibGVfb2ZfTE1NX3BhdGh3YXlfcmVzdWx0cyByZWdpb24sIGZpZy53aWR0aD0yMCxmaWcuaGVpZ2h0PTIwfQ0KZGF0YXRhYmxlKHN1YnNldChsbW1fc3Nnc2VhX3Jlc3VsdHMpLCByb3duYW1lcyA9IEZBTFNFLA0KICAgICAgICAgIGV4dGVuc2lvbnMgPSAnQnV0dG9ucycsIG9wdGlvbnMgPSBsaXN0ICgNCiAgICAgICAgICAgICBwYWdlTGVuZ3RoID0gMTAsDQogICAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JykNCiAgICAgICAgICAgICksDQogICAgICAgICAgY2FwdGlvbiA9ICJERSByZXN1bHRzIGZvciBQYXRod2F5cyIsZmlsdGVyPSd0b3AnKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJFc3RpbWF0ZSIsIlByKD58dHwpIiwiRkRSIiksIGRpZ2l0cz01KQ0KYGBgDQoNCiMjIERLRCAtIG5vcm1hbA0KDQpgYGB7ciB0YWJsZV9vZl9MTU1fcGF0aHdheV9yZXN1bHRzIGNsYXNzLCBmaWcud2lkdGg9MjAsZmlnLmhlaWdodD0yMH0NCmRhdGF0YWJsZShzdWJzZXQobG1tX3NzZ3NlYV9yZXN1bHRzX2QpLCByb3duYW1lcyA9IEZBTFNFLA0KICAgICAgICAgIGV4dGVuc2lvbnMgPSAnQnV0dG9ucycsIG9wdGlvbnMgPSBsaXN0ICgNCiAgICAgICAgICAgICBwYWdlTGVuZ3RoID0gMTAsDQogICAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JykNCiAgICAgICAgICAgICksDQogICAgICAgICAgY2FwdGlvbiA9ICJERSByZXN1bHRzIGZvciBQYXRod2F5cyIsZmlsdGVyPSd0b3AnKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJFc3RpbWF0ZSIsIlByKD58dHwpIiwiRkRSIiksIGRpZ2l0cz01KQ0KYGBgDQoNCiMgOC4zIFZ1bGNhbm9wbG90IG9mIExNTV9QYXRod2F5cw0KDQpgYGB7ciBsbW1fcHdfdnVsY2FubywgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MTB9DQojIENhdGVnb3JpemUgUmVzdWx0cyBiYXNlZCBvbiBQLXZhbHVlICYgRkRSIGZvciBwbG90dGluZw0KZmNfdGhyZXNob2xkID0gMC41DQoNCmxtbV9zc2dzZWFfcmVzdWx0cyRDb2xvciA8LSAiTlMgb3IgRkMgPCAwLjMiDQpsbW1fc3Nnc2VhX3Jlc3VsdHMkQ29sb3JbbG1tX3NzZ3NlYV9yZXN1bHRzJGBQcig+fHR8KWAgPCAwLjA1XSA8LSAiUCA8IDAuMDUiDQpsbW1fc3Nnc2VhX3Jlc3VsdHMkQ29sb3JbbG1tX3NzZ3NlYV9yZXN1bHRzJEZEUiA8IDAuMDVdIDwtICJGRFIgPCAwLjA1Ig0KbG1tX3NzZ3NlYV9yZXN1bHRzJENvbG9yW2xtbV9zc2dzZWFfcmVzdWx0cyRGRFIgPCAwLjAwMV0gPC0gIkZEUiA8IDAuMDAxIg0KbG1tX3NzZ3NlYV9yZXN1bHRzJENvbG9yW2FicyhsbW1fc3Nnc2VhX3Jlc3VsdHMkRXN0aW1hdGUpIDwgZmNfdGhyZXNob2xkXSA8LSAiTlMgb3IgRkMgPCAwLjMiDQpsbW1fc3Nnc2VhX3Jlc3VsdHMkQ29sb3IgPC0gZmFjdG9yKGxtbV9zc2dzZWFfcmVzdWx0cyRDb2xvciwNCiAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIk5TIG9yIEZDIDwgMC4zIiwgIlAgPCAwLjA1IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZEUiA8IDAuMDUiLCAiRkRSIDwgMC4wMDEiKSkNCg0KIyBwaWNrIHRvcCBwdyBmb3IgZWl0aGVyIHNpZGUgb2Ygdm9sY2FubyB0byBsYWJlbA0KIyBvcmRlciBwdyBmb3IgY29udmVuaWVuY2U6DQpsbW1fc3Nnc2VhX3Jlc3VsdHMkaW52ZXJ0X1AgPC0gKC1sb2cxMChsbW1fc3Nnc2VhX3Jlc3VsdHMkYFByKD58dHwpYCkpICogc2lnbihsbW1fc3Nnc2VhX3Jlc3VsdHMkRXN0aW1hdGUpDQp0b3Bfc3Nnc2VhX2cgPC0gYygpDQoNCiNsb29wIGhlcmUgb3ZlciB0ZXN0ZWQgY29uZGl0aW9ucyBpZiBhcHBsaWNhYmxlDQp0b3Bfc3Nnc2VhX2cgPC0gYyh0b3Bfc3Nnc2VhX2csDQogICAgICAgICAgIGxtbV9zc2dzZWFfcmVzdWx0c1ssICdQYXRod2F5J11bDQogICAgICAgICAgICAgICBvcmRlcihsbW1fc3Nnc2VhX3Jlc3VsdHNbLCAnaW52ZXJ0X1AnXSwgZGVjcmVhc2luZyA9IFRSVUUpWzE6MjBdXSwNCiAgICAgICAgICAgbG1tX3NzZ3NlYV9yZXN1bHRzWywgJ1BhdGh3YXknXVsNCiAgICAgICAgICAgICAgIG9yZGVyKGxtbV9zc2dzZWFfcmVzdWx0c1ssICdpbnZlcnRfUCddLCBkZWNyZWFzaW5nID0gRkFMU0UpWzE6MjBdXSkNCg0KdG9wX3NzZ3NlYV9nIDwtIHVuaXF1ZSh0b3Bfc3Nnc2VhX2cpDQpsbW1fc3Nnc2VhX3Jlc3VsdHMgPC0gbG1tX3NzZ3NlYV9yZXN1bHRzWywgLTEqbmNvbChsbW1fc3Nnc2VhX3Jlc3VsdHMpXSAjIHJlbW92ZSBpbnZlcnRfUCBmcm9tIG1hdHJpeA0KDQojbG1tX3NzZ3NlYV9yZXN1bHRzX3NsaWNlIDwtIGxtbV9zc2dzZWFfcmVzdWx0c19zbGljZVtsbW1fc3Nnc2VhX3Jlc3VsdHNfc2xpY2UkRkRSIDwgMSxdDQoNCiMgR3JhcGggcmVzdWx0cw0KcHJpbnQoZ2dwbG90KGxtbV9zc2dzZWFfcmVzdWx0cywNCiAgICAgICBhZXMoeCA9IEVzdGltYXRlLCB5ID0gLWxvZzEwKGBQcig+fHR8KWApLA0KICAgICAgICAgICBjb2xvciA9IENvbG9yLCBsYWJlbCA9IFBhdGh3YXkpKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygwLjUsIC0wLjUpLCBsdHkgPSAiZGFzaGVkIikgKw0KICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IC1sb2cxMCgwLjA1KSwgbHR5ID0gImRhc2hlZCIpICsNCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIGxhYnMoeCA9IHBhc3RlKGxtbV9yZXN1bHRzJENvbnRyYXN0LCAiIEZDIiksDQogICAgICAgICB5ID0gIlNpZ25pZmljYW5jZSwgLWxvZzEwKFApIiwNCiAgICAgICAgIGNvbG9yID0gIlNpZ25pZmljYW5jZSIpICsNCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhgRkRSIDwgMC4wMDFgID0gImRvZGdlcmJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBGRFIgPCAwLjA1YCA9ICJsaWdodGJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBQIDwgMC4wNWAgPSAib3JhbmdlMiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYE5TIG9yIEZDIDwgMC41YCA9ICJncmF5IiksDQogICAgICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDQpKSkgKw0KICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMCwwLjA1KSkpICsNCiAgICBnZW9tX3RleHRfcmVwZWwoZGF0YSA9IHN1YnNldChsbW1fc3Nnc2VhX3Jlc3VsdHMsIENvbG9yID09ICJGRFIgPCAwLjA1IiB8IENvbG9yID09ICJGRFIgPCAwLjAwMSIpLA0KICAgICAgICAgICAgICAgICAgIHBvaW50LnBhZGRpbmcgPSAwLjE1LCBjb2xvciA9ICJibGFjayIsc2l6ZT01LA0KICAgICAgICAgICAgICAgICAgIG1pbi5zZWdtZW50Lmxlbmd0aCA9IC4xLCBib3gucGFkZGluZyA9IC4yLCBsd2QgPSAyLA0KICAgICAgICAgICAgICAgICAgIG1heC5vdmVybGFwcyA9IDUwKSArDQogICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikpDQoNCiMgICAgK2ZhY2V0X3dyYXAoflN1YnNldCwgc2NhbGVzID0gImZyZWVfeSIpKQ0KYGBgDQoNCiMgOC40IGhlYXRtYXAgb2YgcGF0aHdheXMNCg0KYGBge3IgcHdfaGVhdG1hcCwgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MjB9DQogIGFjdGl2ZV9wdzwtZmlsdGVyKGxtbV9zc2dzZWFfcmVzdWx0cywgYFByKD58dHwpYCA8IDAuMDAxKSRQYXRod2F5DQogIGFjdGl2ZV9wdzwtZmlsdGVyKGxtbV9zc2dzZWFfcmVzdWx0cywgRkRSIDwgMC4wMDEgKSRQYXRod2F5DQogICNhY3RpdmVfcHc8LWZpbHRlcihsbW1fc3Nnc2VhX3Jlc3VsdHMsIENvbG9yID09ICJGRFIgPCAwLjAwMSIpJFBhdGh3YXkNCiAgDQogIA0KICBhY3RpdmVfcHc8LXRvcF9zc2dzZWFfZw0KICANCiAgaWYgKGxlbmd0aChhY3RpdmVfcHcpPjEpIHsNCiAgDQogICAgcHJpbnQoImdvIikNCiAgICANCiAgcHdfbWF0cml4PC1hc3NheURhdGFFbGVtZW50KGdlbmVTZXRPYmosIGVsdCA9ICJleHBycyIpDQoNCiAgcGhlYXRtYXAocHdfbWF0cml4W2FjdGl2ZV9wdyxdLA0KICAgICAgICAgc2NhbGUgPSAicm93IiwNCiAgICAgICAgIHNob3dfcm93bmFtZXMgPSBUUlVFLCBzaG93X2NvbG5hbWVzID0gVFJVRSwNCiAgICAgICAgIGZvbnRzaXplX3JvdyA9IDEwLA0KICAgICAgICAgYm9yZGVyX2NvbG9yID0gTkEsDQogICAgICAgICBjbHVzdGVyaW5nX21ldGhvZCA9ICJhdmVyYWdlIiwNCiAgICAgICAgICNjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3MgPSAiY29ycmVsYXRpb24iLA0KICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImV1Y2xpZGVhbiIsDQogICAgICAgICBjdXRyZWVfY29scyA9IDIsIGN1dHJlZV9yb3dzID0gMiwNCiAgICAgICAgIGJyZWFrcyA9IHNlcSgtMywgMywgMC4wNSksDQogICAgICAgICAjY29sb3IgPSBjb2xvclJhbXBQYWxldHRlKGMoInB1cnBsZTMiLCAiYmxhY2siLCAieWVsbG93MiIpKSgxMjApLA0KICAgICAgICAgbWFpbiA9ICJIZWF0bWFwIG9mIHNlbGVjdGVkIFBhdGh3YXlzIiwNCiAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gY29sb3JfbGlzdCwNCiAgICAgICAgIGFubm90YXRpb25fY29sID0gcERhdGEodGFyZ2V0X0RhdGEpWywgYW5uX25hbWVzXSkNCiAgDQogIH1lbHNlew0KICAgIHByaW50KCJObyBzaWduaWZpY2FudCByZXN1bHRzIHRvIGRpc3BsYXkiKQ0KICB9DQpgYGANCg0KIyA5IFNwYXRpYWwgRGVjb252b2x1dGlvbg0KDQojIyA5LjEgQ2FsY3VsYXRlIGJhY2tncm91bmRzDQoNCmBgYHtyIHNwYXRpYWxfZGVjb25fYmd9DQpiZyA9IGRlcml2ZV9HZW9NeF9iYWNrZ3JvdW5kKA0KICBub3JtID0gYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YSAsIGVsdCA9ICJxX25vcm0iKSwNCiAgcHJvYmVwb29sID0gZkRhdGEodGFyZ2V0X0RhdGEpJE1vZHVsZSwNCiAgbmVnbmFtZXMgPSBjKCJOZWdQcm9iZS1DVFAwMSIsIk5lZ1Byb2JlLUtpbG8iLCJOZWdhdGl2ZSBQcm9iZSIsICJOZWdQcm9iZS1XVFgiICkpDQogICNuZWduYW1lcyA9ICJOZWdQcm9iZS1XVFgiKQ0KDQpgYGANCg0KIyMgOS4yIExvYWQgY2VsbCBwcm9maWxlDQoNCkEgImNlbGwgcHJvZmlsZSBtYXRyaXgiIGlzIGEgcHJlLWRlZmluZWQgbWF0cml4IHRoYXQgc3BlY2lmaWVzIHRoZQ0KZXhwZWN0ZWQgZXhwcmVzc2lvbiBwcm9maWxlcyBvZiBlYWNoIGNlbGwgdHlwZSBpbiB0aGUgZXhwZXJpbWVudC4gVGhlDQpTcGF0aWFsRGVjb24gbGlicmFyeSBjb21lcyB3aXRoIG9uZSBzdWNoIG1hdHJpeCBwcmUtbG9hZGVkLCB0aGUNCiJTYWZlVE1FIiBtYXRyaXgsIGRlc2lnbmVkIGZvciBlc3RpbWF0aW9uIG9mIGltbXVuZSBhbmQgc3Ryb21hIGNlbGxzIGluDQp0aGUgdHVtb3IgbWljcm9lbnZpcm9ubWVudC4gKFRoaXMgbWF0cml4IHdhcyBkZXNpZ25lZCB0byBhdm9pZCBnZW5lcw0KY29tbW9ubHkgZXhwcmVzc2VkIGJ5IGNhbmNlciBjZWxsczsgc2VlIHRoZSBTcGF0aWFsRGVjb24gbWFudXNjcmlwdCBmb3INCmRldGFpbHMuKS4gT3RoZXJ3aXNlLCBsb2FkIHNwZWNpZmljIHByb2ZpbGVzIGZyb20NCjxodHRwczovL2dpdGh1Yi5jb20vTmFub3N0cmluZy1CaW9zdGF0cy9DZWxsUHJvZmlsZUxpYnJhcnkvdHJlZS9OZXdQcm9maWxlTWF0cmljZXM+DQoNCmBgYHtyIGxvYWRfY2VsbF9wcm9maWxlc30NCiNzYWZlVE1FDQpkYXRhKCJzYWZlVE1FIikNCmRhdGEoInNhZmVUTUUubWF0Y2hlcyIpDQpjdXJyZW50X2NlbGxfcHJvZmlsZTwtc2FmZVRNRQ0KDQojc2VlOiBodHRwczovL2dpdGh1Yi5jb20vTmFub3N0cmluZy1CaW9zdGF0cy9DZWxsUHJvZmlsZUxpYnJhcnkvdHJlZS9OZXdQcm9maWxlTWF0cmljZXMNCg0KIyBjdXJyZW50X2NlbGxfcHJvZmlsZSA8LSBkb3dubG9hZF9wcm9maWxlX21hdHJpeChzcGVjaWVzID0gIkh1bWFuIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWdlX2dyb3VwID0gIkFkdWx0IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF0cml4bmFtZSA9ICJCbGFkZGVyX01DQSIpDQoNCmhlYXRtYXAoc3dlZXAoY3VycmVudF9jZWxsX3Byb2ZpbGUsIDEsIGFwcGx5KGN1cnJlbnRfY2VsbF9wcm9maWxlLCAxLCBtYXgpLCAiLyIpLA0KICAgICAgICBsYWJSb3cgPSBOQSwgbWFyZ2lucyA9IGMoMTAsIDUpLCBjZXhDb2wgPSAwLjcpDQpgYGANCg0KIyA5LjMgUnVuIHNwYXRpYWwgZGVjb252b2x1dGlvbg0KDQpgYGB7ciBzcGF0aWFsX2RlY29uX3J1bn0NCiMgdmVjdG9yIGlkZW50aWZ5aW5nIHB1cmUgdHVtb3Igc2VnbWVudHM6DQojdGFyZ2V0X0RhdGEkaXN0dW1vciA9IHRhcmdldF9EYXRhJEFOTjMgPT0gIkNPUkUiICYgdGFyZ2V0X0RhdGEkQU5OMSA9PSAiUGFuQ0srIg0KDQpyZXMgPSBydW5zcGF0aWFsZGVjb24ob2JqZWN0ID0gdGFyZ2V0X0RhdGEsDQogICAgICAgICAgICAgICAgICAgICAgbm9ybV9lbHQgPSAicV9ub3JtIiwNCiAgICAgICAgICAgICAgICAgICAgICByYXdfZWx0ID0gImV4cHJzIiwNCiAgICAgICAgICAgICAgICAgICAgICAjaXNfcHVyZV90dW1vciA9IHRhcmdldF9EYXRhJGlzdHVtb3IsDQogICAgICAgICAgICAgICAgICAgICAgY2VsbF9jb3VudHMgPSB0YXJnZXRfRGF0YSRudWNsZWksDQogICAgICAgICAgICAgICAgICAgICAgWCA9IGN1cnJlbnRfY2VsbF9wcm9maWxlLA0KICAgICAgICAgICAgICAgICAgICAgIGNlbGxtZXJnZXMgPSBzYWZlVE1FLm1hdGNoZXMsICAgICAgICAgICAgICAjIHNhZmVUTUUubWF0Y2hlcyBvYmplY3QsIHVzZWQgYnkgZGVmYXVsdA0KICAgICAgICAgICAgICAgICAgICAgICNuX3R1bW9yX2NsdXN0ZXJzID0gNSwgICAgICAgICAgICAgICAgICAgICAgIyBob3cgbWFueSBkaXN0aW5jdCB0dW1vciBwcm9maWxlcyB0byBhcHBlbmQgdG8gc2FmZVRNRQ0KICAgICAgICAgICAgICAgICAgICAgIGFsaWduX2dlbmVzID0gVFJVRSkNCg0KDQpgYGANCg0KIyA5LjMuMSBTcGF0aWFsIGRlY29udm9sdXRpb24gaGVhdG1hcHMgey50YWJzZXQgLnRhYnNldC1waWxsc30NCg0KIyMgQWJ1bmRhbmNlDQoNCmBgYHtyIHNwYXRpYWxfZGVjb25faGVhdG1hcCwgZmlnLndpZHRoPTI1LGZpZy5oZWlnaHQ9MTV9DQojIE5PVEU6IGNoZWNrIGNsdXN0ZXJpbmcuLiB3aHkgZGlmZmVyZW50Pw0KDQojc2V0IGRpc3BsYXkgdGhyZXNob2xkcw0KdGhyZXNoIDwtIHNpZ25pZihxdWFudGlsZShyZXMkYmV0YSwgMC45NyksIDIpDQoNCiMgcGxvdCBzdG9yZWQgdG8ga2VlcCBjbHVzdGVyaW5nIGZvciBsYXRlcg0KcDE8LXBoZWF0bWFwKHBtaW4odChyZXMkYmV0YSksdGhyZXNoKSwNCiAgICAgICAgICNzY2FsZSA9ICJyb3ciLCANCiAgICAgICAgIGN1dHJlZV9jb2xzID0gMywNCiAgICAgICAgIGN1dHJlZV9yb3dzID0gMiwNCiAgICAgICAgIGZvbnRzaXplX3JvdyA9IDEyLA0KICAgICAgICAgc2hvd19yb3duYW1lcyA9IFRSVUUsIHNob3dfY29sbmFtZXMgPSBUUlVFLA0KICAgICAgICAgYW5nbGVfY29sID0gIjkwIiwNCiAgICAgICAgIGJvcmRlcl9jb2xvciA9IE5BLA0KICAgICAgICAgI2NsdXN0ZXJpbmdfbWV0aG9kID0gImF2ZXJhZ2UiLA0KICAgICAgICAgI2NsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgICAjY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImNvcnJlbGF0aW9uIiwNCiAgICAgICAgIGxlZ2VuZF9icmVha3MgPSBjKHJvdW5kKHNlcSgwLCB0aHJlc2gsIGxlbmd0aC5vdXQgPSA1KSlbLTVdLCB0aHJlc2gpLA0KICAgICAgICAgbGVnZW5kX2xhYmVscyA9IGMocm91bmQoc2VxKDAsIHRocmVzaCwgbGVuZ3RoLm91dCA9IDUpKVstNV0sIHBhc3RlMCgiQWJ1bmRhbmNlIHNjb3JlcyxcbnRydW5jYXRlZCBhYm92ZSBhdCAiLCB0aHJlc2gpKSwNCiAgICAgICAgICNicmVha3MgPSBzZXEoMCwgNSwgMSksDQogICAgICAgICBjb2xvciA9IGNvbG9yUmFtcFBhbGV0dGUoYygid2hpdGUiLCJkYXJrYmx1ZSIpKSgxMDApLA0KICAgICAgICAgYW5ub3RhdGlvbl9jb2xvcnMgPSBjb2xvcl9saXN0LA0KICAgICAgICAgYW5ub3RhdGlvbl9jb2wgPSBwRGF0YSh0YXJnZXRfRGF0YSlbLCBhbm5fbmFtZXNdDQogICAgICAgICApDQojcDENCmBgYA0KDQojIyBQcm9wb3J0aW9uYWwNCg0KYGBge3Igc3BhdGlhbF9kZWNvbl9wcm9waGVhdG1hcCwgZmlnLndpZHRoPTI1LGZpZy5oZWlnaHQ9MTV9DQojIHByb3BvcnRpb25zOg0KcHJvcHMgPC0gcmVwbGFjZShyZXMkcHJvcF9vZl9ub250dW1vciwgaXMubmEocmVzJHByb3Bfb2Zfbm9udHVtb3IpLCAwKQ0KDQpwMjwtcGhlYXRtYXAodChwcm9wcyksDQogICAgICAgICAjc2NhbGUgPSAicm93IiwgDQogICAgICAgICBjdXRyZWVfY29scyA9IDMsDQogICAgICAgICBjdXRyZWVfcm93cyA9IDIsDQogICAgICAgICBmb250c2l6ZV9yb3cgPSAxMiwNCiAgICAgICAgIHNob3dfcm93bmFtZXMgPSBUUlVFLCBzaG93X2NvbG5hbWVzID0gVFJVRSwNCiAgICAgICAgIGFuZ2xlX2NvbCA9ICI5MCIsDQogICAgICAgICBib3JkZXJfY29sb3IgPSBOQSwNCiAgICAgICAgICNjbHVzdGVyaW5nX21ldGhvZCA9ICJhdmVyYWdlIiwNCiAgICAgICAgICNjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3MgPSAiY29ycmVsYXRpb24iLA0KICAgICAgICAgI2NsdXN0ZXJpbmdfZGlzdGFuY2VfY29scyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgICBsZWdlbmRfYnJlYWtzID0gcm91bmQoc2VxKDAsIG1heChwcm9wcykgKiAwLjk5LCBsZW5ndGgub3V0ID0gNSksIDIpLA0KICAgICAgICAgbGVnZW5kX2xhYmVscyA9IGMocm91bmQoc2VxKDAsIG1heChwcm9wcyksIGxlbmd0aC5vdXQgPSA1KSwgMilbLTVdLCAiUHJvcG9ydGlvbiBvZiBhbGxcbmZpdHRlZCBwb3B1bGF0aW9ucyIpLA0KICAgICAgICAgY29sb3IgPSBjb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwiZGFya2JsdWUiKSkoMTAwKSwNCiAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gY29sb3JfbGlzdCwNCiAgICAgICAgIGFubm90YXRpb25fY29sID0gcERhdGEodGFyZ2V0X0RhdGEpWywgYW5uX25hbWVzXSkNCg0KI3AyDQoNCmBgYA0KDQojIyBTY2FsZWQNCg0KYGBge3Igc3BhdGlhbF9kZWNvbl9zY2FsZWRoZWF0bWFwLCBmaWcud2lkdGg9MjUsZmlnLmhlaWdodD0xNX0NCiMgc2NhbGVkIGFidW5kYW5jZXM6DQplcHNpbG9uIDwtIG1pbihyZXMkYmV0YVtyZXMkYmV0YSA+IDBdKQ0KbWF0IDwtIHN3ZWVwKHJlcyRiZXRhLCAxLCBwbWF4KGFwcGx5KHJlcyRiZXRhLCAxLCBtYXgpLCBlcHNpbG9uKSwgIi8iKQ0KDQpwaGVhdG1hcCh0KG1hdCksDQogICAgICAgICAjc2NhbGUgPSAicm93IiwNCiAgICAgICAgIGN1dHJlZV9jb2xzID0gMywNCiAgICAgICAgIGN1dHJlZV9yb3dzID0gMywNCiAgICAgICAgIGZvbnRzaXplX3JvdyA9IDEyLA0KICAgICAgICAgc2hvd19yb3duYW1lcyA9IFRSVUUsIHNob3dfY29sbmFtZXMgPSBUUlVFLA0KICAgICAgICAgYW5nbGVfY29sID0gIjkwIiwNCiAgICAgICAgIGJvcmRlcl9jb2xvciA9IE5BLA0KICAgICAgICAgI2NsdXN0ZXJpbmdfbWV0aG9kID0gImF2ZXJhZ2UiLA0KICAgICAgICAgI2NsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgICAjY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImNvcnJlbGF0aW9uIiwNCiAgICAgICAgIGxlZ2VuZF9icmVha3MgPSBjKHJvdW5kKHNlcSgwLCAxLCBsZW5ndGgub3V0ID0gNSksIDIpWy01XSwgMSksDQogICAgICAgICBsZWdlbmRfbGFiZWxzID0gYyhyb3VuZChzZXEoMCwgMSwgbGVuZ3RoLm91dCA9IDUpLCAyKVstNV0sICJTY2FsZWQgYWJ1bmRhbmNlXG4ocmF0aW8gdG8gbWF4KSIpLA0KICAgICAgICAgY29sb3IgPSBjb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwiZGFya2JsdWUiKSkoMTAwKSwNCiAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gY29sb3JfbGlzdCwNCiAgICAgICAgIGFubm90YXRpb25fY29sID0gcERhdGEodGFyZ2V0X0RhdGEpWywgYW5uX25hbWVzXSkNCg0KYGBgDQoNCiMgOS40IEJhcnBsb3RzIHsudGFic2V0IC50YWJzZXQtcGlsbHN9DQoNCiMjIGFidW5kYW5jZQ0KDQpgYGB7ciBTRF9hYnVuZGFuY2VfYmFycGxvdCwgZmlnLndpZHRoPTI1LGZpZy5oZWlnaHQ9MTV9DQojIGRlZmluZSB2YXJpYWJsZXMgdG8gc2hvdyBpbiBoZWF0bWFwczoNCnZhcmlhYmxlc190b19wbG90IDwtIGMoInNsaWRlX25hbWUiLCAicmVnaW9uIiwgImNsYXNzIikNCg0KY29sIDwtIGNlbGxjb2xzDQoNCmxheW91dChtYXRyaXgoYygxLCAyLCAzLCAzKSwgbnJvdyA9IDIpLA0KICAgICAgIHdpZHRocyA9IGMoMTAsIDMsIDEwLCAzKSwNCiAgICAgICBoZWlnaHRzID0gYygxLCA4LCAxMCksDQogICAgICApDQoNCnBhcihtYXIgPSBjKDAsIDguMiwgMCwgMC4yKSkNCnBsb3QocDEkdHJlZV9jb2wsIGxhYmVscyA9IEYsIG1haW4gPSAiIiwgeWxhYiA9ICIiLCB5YXh0ID0gIm4iKQ0KcGFyKG1hciA9IGMoMTUsIDgsIDAsIDApKQ0KDQojIGRhdGEgdG8gcGxvdDoNCm1hdCA8LSB0KHJlcyRiZXRhKVssIHAxJHRyZWVfY29sJG9yZGVyXQ0KIyBpbmZlciBzY2FsZSBvZiBuZWdhdGl2ZSB5LWF4aXMgZm9yIGFubm90YXRpb24gY29sb3JiYXJzDQp5bWluIDwtIC1tYXgoY29sU3VtcyhtYXQpKSAqIDAuMTUNCmlmICghaXMuZmluaXRlKHltaW4pKSB7DQogIHltaW4gPC0gMA0KfQ0KDQojIGRyYXcgYmFycGxvdDoNCmJwIDwtIGJhcnBsb3QobWF0LA0KICAgICAgICAgICAgICBjZXgubGFiID0gMS41LA0KICAgICAgICAgICAgICBjb2wgPSBjb2wsIGJvcmRlciA9IE5BLA0KICAgICAgICAgICAgICBjZXgubmFtZXMgPSAxLjEsDQogICAgICAgICAgICAgIGxhcyA9IDIsIG1haW4gPSAiIiwgeWxhYiA9ICJBYnVuZGFuY2Ugc2NvcmVzIiwNCiAgICAgICAgICAgICAgeWxpbSA9IGMoeW1pbiwgbWF4KGNvbFN1bXMobWF0KSkpDQopDQoNCg0KIyBhZGQgY29sb3IgYmFycyBmb3IgYW5ub3RhdGlvbnMNCmZvciAobmFtZSBpbiByZXYodmFyaWFibGVzX3RvX3Bsb3QpKSB7DQogIHlyYW5nZSA8LSBzZXEoeW1pbiAvIDMsIHltaW4sIGxlbmd0aC5vdXQgPSBsZW5ndGgodmFyaWFibGVzX3RvX3Bsb3QpICsgMSlbbWF0Y2gobmFtZSwgdmFyaWFibGVzX3RvX3Bsb3QpICsgYygwLCAxKV0NCiAgeHdpZHRoIDwtIChicFsyXSAtIGJwWzFdKSAvIDINCiAgZm9yIChpIGluIDE6bmNvbChtYXQpKSB7DQogICAgcmVjdChicFtpXSAtIHh3aWR0aCwgeXJhbmdlWzJdLCBicFtpXSArIHh3aWR0aCwgeXJhbmdlWzFdLA0KICAgICAgICAgIyBib3JkZXIgPSBOQSwgY29sID0gYW5uX2NvbG9yc1tbbmFtZV1dW3NlZ21lbnRBbm5vdGF0aW9uc1ttYXRjaChjb2xuYW1lcyhtYXQpW2ldLCBzZWdtZW50QW5ub3RhdGlvbnMkc2VnbWVudElEKSwgbmFtZV1dDQogICAgICAgICAjYm9yZGVyID0gTkEsIGNvbCA9IGFubl9jb2xvcnNbW25hbWVdXVthbm5bcDEkdHJlZV9jb2wkb3JkZXJbaV0sIG5hbWVdXQ0KICAgICAgICAgYm9yZGVyID0gTkEsIGNvbCA9IGNvbG9yX2xpc3RbW25hbWVdXVthbm5bY29sbmFtZXMobWF0KVtpXSwgbmFtZV1dDQogICAgKQ0KICB9DQp9DQpheGlzKDIsDQogICAgIGF0ID0gc2VxKHltaW4gLyAzLCB5bWluLCBsZW5ndGgub3V0ID0gbGVuZ3RoKHZhcmlhYmxlc190b19wbG90KSArIDIpWy1jKDEsIGxlbmd0aCh2YXJpYWJsZXNfdG9fcGxvdCkgKyAyKV0sDQogICAgIGxhcyA9IDIsIGxhYmVscyA9IHZhcmlhYmxlc190b19wbG90LCBsdHkgPSAwLCBjZXguYXhpcyA9IDEuMg0KKQ0KDQojZHJhdyBhIGxlZ2VuZDoNCnBhcihtYXIgPSBjKDAuMSwgMC4xLCAwLjEsIDAuMSkpDQpmcmFtZSgpDQpsZWdlbmRjb2xzIDwtIGxlZ2VuZG5hbWVzIDwtIGMoKQ0KI2ZvciAobmFtZSBpbiByZXYobmFtZXMoYW5uX2NvbG9ycykpKSB7DQpmb3IgKG5hbWUgaW4gYygic2xpZGVfbmFtZSIsICJyZWdpb24iLCJjbGFzcyIpKSB7DQogIGxlZ2VuZGNvbHMgPC0gYyhsZWdlbmRjb2xzLCBOQSwgY29sb3JfbGlzdFtbbmFtZV1dLCBOQSkNCiAgbGVnZW5kbmFtZXMgPC0gYyhsZWdlbmRuYW1lcywgbmFtZSwgbmFtZXMoY29sb3JfbGlzdFtbbmFtZV1dKSwgTkEpDQp9DQpsZWdlbmQoImNlbnRlciIsDQogICAgICAgcGNoID0gMTUsDQogICAgICAgY2V4ID0gMS41LA0KICAgICAgIGNvbCA9IGMobGVnZW5kY29scywgcmVwKE5BLCAxKSwgcmV2KGNvbCkpLA0KICAgICAgIGxlZ2VuZCA9IGMobGVnZW5kbmFtZXMsICJDZWxsIHR5cGUiLCByZXYobmFtZXMoY29sKSkpLA0KKQ0KYGBgDQoNCiMjIHByb3BvcnRpb25hbA0KDQpgYGB7ciBTRF9wcm9wX2JhcnBsb3QsIGZpZy53aWR0aD0yNSxmaWcuaGVpZ2h0PTE1fQ0KIyBkZWZpbmUgdmFyaWFibGVzIHRvIHNob3cgaW4gaGVhdG1hcHM6DQp2YXJpYWJsZXNfdG9fcGxvdCA8LSBjKCJzbGlkZSBuYW1lIiwgInNlZ21lbnQiLCJwaGVubyIpDQoNCmxheW91dChtYXRyaXgoYygxLCAyLCAzLCAzKSwgbnJvdyA9IDIpLA0KICAgICAgIHdpZHRocyA9IGMoMTAsIDMsIDEwLCAzKSwNCiAgICAgICBoZWlnaHRzID0gYygxLCA4LCAxMCksDQogICAgICApDQoNCnBhcihtYXIgPSBjKDAsIDguMiwgMCwgMC4yKSkNCnBsb3QocDIkdHJlZV9jb2wsIGxhYmVscyA9IEYsIG1haW4gPSAiIiwgeWxhYiA9ICIiLCB5YXh0ID0gIm4iKQ0KcGFyKG1hciA9IGMoMTUsIDgsIDAsIDApKQ0KDQojIGRhdGEgdG8gcGxvdDoNCm1hdCA8LSB0KHJlcyRwcm9wX29mX25vbnR1bW9yKVssIHAyJHRyZWVfY29sJG9yZGVyXQ0KICBtYXQgPC0gcmVwbGFjZShtYXQsIGlzLm5hKG1hdCksIDApDQogICMgaW5mZXIgc2NhbGUgb2YgbmVnYXRpdmUgeS1heGlzIGZvciBhbm5vdGF0aW9uIGNvbG9yYmFycw0KICB5bWluIDwtIC0wLjE1DQoNCiMgZHJhdyBiYXJwbG90Og0KYnAgPC0gYmFycGxvdChtYXQsDQogICAgICAgICAgICAgIGNleC5sYWIgPSAxLjUsDQogICAgICAgICAgICAgIGNvbCA9IGNvbCwgYm9yZGVyID0gTkEsDQogICAgICAgICAgICAgIGNleC5uYW1lcyA9IDEuMSwNCiAgICAgICAgICAgICAgbGFzID0gMiwgbWFpbiA9ICIiLCB5bGFiID0gIlByb3BvcnRpb24gb2YgZml0dGVkIGNlbGxzIiwNCiAgICAgICAgICAgICAgeWxpbSA9IGMoeW1pbiwgbWF4KGNvbFN1bXMobWF0KSkpDQopDQoNCiMgYWRkIGNvbG9yIGJhcnMgZm9yIGFubm90YXRpb25zDQpmb3IgKG5hbWUgaW4gcmV2KHZhcmlhYmxlc190b19wbG90KSkgew0KICB5cmFuZ2UgPC0gc2VxKHltaW4gLyAzLCB5bWluLCBsZW5ndGgub3V0ID0gbGVuZ3RoKHZhcmlhYmxlc190b19wbG90KSArIDEpW21hdGNoKG5hbWUsIHZhcmlhYmxlc190b19wbG90KSArIGMoMCwgMSldDQogIHh3aWR0aCA8LSAoYnBbMl0gLSBicFsxXSkgLyAyDQogIGZvciAoaSBpbiAxOm5jb2wobWF0KSkgew0KICAgIHJlY3QoYnBbaV0gLSB4d2lkdGgsIHlyYW5nZVsyXSwgYnBbaV0gKyB4d2lkdGgsIHlyYW5nZVsxXSwNCiAgICAgICAgICMgYm9yZGVyID0gTkEsIGNvbCA9IGFubl9jb2xvcnNbW25hbWVdXVtzZWdtZW50QW5ub3RhdGlvbnNbbWF0Y2goY29sbmFtZXMobWF0KVtpXSwgc2VnbWVudEFubm90YXRpb25zJHNlZ21lbnRJRCksIG5hbWVdXQ0KICAgICAgICAgI2JvcmRlciA9IE5BLCBjb2wgPSBhbm5fY29sb3JzW1tuYW1lXV1bYW5uW3AyJHRyZWVfY29sJG9yZGVyW2ldLCBuYW1lXV0NCiAgICAgICAgIGJvcmRlciA9IE5BLCBjb2wgPSBjb2xvcl9saXN0W1tuYW1lXV1bYW5uW2NvbG5hbWVzKG1hdClbaV0sIG5hbWVdXQ0KICAgICkNCiAgfQ0KfQ0KYXhpcygyLA0KICAgICBhdCA9IHNlcSh5bWluIC8gMywgeW1pbiwgbGVuZ3RoLm91dCA9IGxlbmd0aCh2YXJpYWJsZXNfdG9fcGxvdCkgKyAyKVstYygxLCBsZW5ndGgodmFyaWFibGVzX3RvX3Bsb3QpICsgMildLA0KICAgICBsYXMgPSAyLCBsYWJlbHMgPSB2YXJpYWJsZXNfdG9fcGxvdCwgbHR5ID0gMCwgY2V4LmF4aXMgPSAxLjINCikNCg0KDQojZHJhdyBhIGxlZ2VuZDoNCnBhcihtYXIgPSBjKDAuMSwgMC4xLCAwLjEsIDAuMSkpDQpmcmFtZSgpDQpsZWdlbmRjb2xzIDwtIGxlZ2VuZG5hbWVzIDwtIGMoKQ0KI2ZvciAobmFtZSBpbiByZXYobmFtZXMoYW5uX2NvbG9ycykpKSB7DQpmb3IgKG5hbWUgaW4gYygic2xpZGUgbmFtZSIsICJzZWdtZW50IiwicGhlbm8iKSkgew0KICBsZWdlbmRjb2xzIDwtIGMobGVnZW5kY29scywgTkEsIGNvbG9yX2xpc3RbW25hbWVdXSwgTkEpDQogIGxlZ2VuZG5hbWVzIDwtIGMobGVnZW5kbmFtZXMsIG5hbWUsIG5hbWVzKGNvbG9yX2xpc3RbW25hbWVdXSksIE5BKQ0KfQ0KbGVnZW5kKCJjZW50ZXIiLA0KICAgICAgIHBjaCA9IDE1LA0KICAgICAgIGNleCA9IDEuNCwNCiAgICAgICBjb2wgPSBjKGxlZ2VuZGNvbHMsIHJlcChOQSwgMSksIHJldihjb2wpKSwNCiAgICAgICBsZWdlbmQgPSBjKGxlZ2VuZG5hbWVzLCAiQ2VsbCB0eXBlIiwgcmV2KG5hbWVzKGNvbCkpKSwNCikNCmBgYA0KDQojIDEwIENvZGUgJiBWZXJzaW9ucw0KDQpQaXBlbGluZXZlcnNpb246IHYxIGJhc2VkIG9uOg0KPGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9kZXZlbC93b3JrZmxvd3MvdmlnbmV0dGVzL0dlb014V29ya2Zsb3dzL2luc3QvZG9jL0dlb214VG9vbHNfUk5BLU5HU19BbmFseXNpcy5odG1sPg0KDQpUaGUgdW5kZXJseWluZyBjb2RlIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gdGhlICdDb2RlJywgYnV0dG9uIG9uIHRoZSB0b3ANCm9mIHRoaXMgcGFnZS4gQ2hvb3NlIG9wdGlvbiAnZG93bmxvYWQgUm1kJyB0byBkb3dubG9hZCB0aGUgZnVsbCBwaXBlbGluZQ0Kd2hpY2ggY2FuIGJlIG9wZW5lZCBpbiBSIG9yIFJzdHVkaW8uIFNvbWUgZmlsZXBhdGhzIGFyZSBoYXJkY29kZWQgYW5kDQpuZWVkIHRvIGJlIGNoYW5nZWQgYWNjb3JkaW5nIHRvIHlvdXIgc2V0dXAuDQoNCiMgMTAuMSBSIHNlc3Npb24gaW5mb3JtYXRpb24NCg0KYGBge3Igc2Vzc2lvbl9pbmZvfQ0Kc2Vzc2lvbkluZm8oKQ0KYGBgDQoNCiMjIDEwLjIgUmVmZXJlbmNlcw0KDQohW10oQzovVXNlcnMvcGtsb29zdGVybWFuL0RvY3VtZW50cy9nZW5lcmFsX3dvcmtmbG93L2RlY29yYXRpb24tc3Ryb2tlLWZsYXQtdjIucG5nKQ0KDQpgYGB7cn0NCmtuaXRyOjprbml0X2V4aXQoKQ0KYGBgDQoNCmBgYHtyfQ0Ka25pdHI6OmtuaXRfZXhpdCgpDQpgYGANCg0KIyA5LjAgcmVsb2FkIGRhdGENCg0KYGBge3Igc3BhdGlhbF9kZWNvbl9wcmVwYXJlfQ0KI3JlbG9hZCBkYXRhDQojIERhdGEgPC0NCiMgICByZWFkTmFub1N0cmluZ0dlb014U2V0KGRjY0ZpbGVzID0gRENDRmlsZXMsDQojICAgICAgICAgICAgICAgICAgICAgICAgICBwa2NGaWxlcyA9IFBLQ0ZpbGVzLA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgcGhlbm9EYXRhRmlsZSA9IFNhbXBsZUFubm90YXRpb25GaWxlLA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgcGhlbm9EYXRhU2hlZXQgPSAiU2hlZXQxIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgIHBoZW5vRGF0YURjY0NvbE5hbWUgPSAiU2FtcGxlX0lEIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgIHByb3RvY29sRGF0YUNvbE5hbWVzID0gYygiYW9pIiwgInJvaSIpLA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgZXhwZXJpbWVudERhdGFDb2xOYW1lcyA9IGMoInBhbmVsIikpDQojIA0KIyBwa2NzIDwtIGFubm90YXRpb24oRGF0YSkNCiMgbW9kdWxlcyA8LSBnc3ViKCIucGtjIiwgIiIsIHBrY3MpDQojIA0KIyAjc2hpZnQgYW55IGV4cHJlc3Npb24gY291bnRzIHdpdGggYSB2YWx1ZSBvZiAwIHRvIDEgdG8gZW5hYmxlIGluIGRvd25zdHJlYW0gdHJhbnNmb3JtYXRpb25zLg0KIyBEYXRhIDwtIHNoaWZ0Q291bnRzT25lKERhdGEsIHVzZURBTG9naWMgPSBUUlVFKQ0KIyANCiMgI2NvbGxhcHNfdGFyZ2V0cw0KIyB0YXJnZXRfRGF0YSA8LSBhZ2dyZWdhdGVDb3VudHMoRGF0YSkNCiMgZGltKHRhcmdldF9EYXRhKQ0KIyANCiMgI25vcm1hbGl6ZQ0KIyB0YXJnZXRfRGF0YSA8LSBub3JtYWxpemUodGFyZ2V0X0RhdGEgLCBkYXRhX3R5cGUgPSAiUk5BIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtX21ldGhvZCA9ICJxdWFudCIsIA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlc2lyZWRRdWFudGlsZSA9IC43NSwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b0VsdCA9ICJxX25vcm0iKQ0KYGBgDQoNCiMgOS40IFJ1biBhZHZhbmNlZCBzcGF0aWFsIGRldm9uY29sdXRpb24NCg0KYGBge3J9DQojIHZlY3RvciBpZGVudGlmeWluZyBwdXJlIHR1bW9yIHNlZ21lbnRzOg0KdGFyZ2V0X0RhdGEkaXN0dW1vciA9IHRhcmdldF9EYXRhJEFOTjIgPT0gIkVwaXRoZWxpdW0iDQoNCiMgcnVuIHNwYXRpYWxkZWNvbiB3aXRoIGFsbCB0aGUgYmVsbHMgYW5kIHdoaXN0bGVzOg0KcmVzdGlscyA9IHJ1bnNwYXRpYWxkZWNvbihvYmplY3QgPSB0YXJnZXRfRGF0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbm9ybV9lbHQgPSAicV9ub3JtIiwgICAgICAgICAgICAgICAgICAgICMgbm9ybWFsaXplZCBkYXRhDQogICAgICAgICAgICAgICAgICAgICAgICAgIHJhd19lbHQgPSAiZXhwcnMiLCAgICAgICAgICAgICAgICAgICAgICAjIGV4cGVjdGVkIGJhY2tncm91bmQgY291bnRzIGZvciBldmVyeSBkYXRhIHBvaW50IGluIG5vcm0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgWCA9IHNhZmVUTUUsICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgc2FmZVRNRSBtYXRyaXgsIHVzZWQgYnkgZGVmYXVsdA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjZWxsbWVyZ2VzID0gc2FmZVRNRS5tYXRjaGVzLCAgICAgICAgICAgIyBzYWZlVE1FLm1hdGNoZXMgb2JqZWN0LCB1c2VkIGJ5IGRlZmF1bHQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbF9jb3VudHMgPSB0YXJnZXRfRGF0YSRudWNsZWksICAgICAgIyBudWNsZWkgY291bnRzLCB1c2VkIHRvIGVzdGltYXRlIHRvdGFsIGNlbGxzDQogICAgICAgICAgICAgICAgICAgICAgICAgIGlzX3B1cmVfdHVtb3IgPSB0YXJnZXRfRGF0YSRpc3R1bW9yLCAgICMgaWRlbnRpdGllcyBvZiB0aGUgVHVtb3Igc2VnbWVudHMvb2JzZXJ2YXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIG5fdHVtb3JfY2x1c3RlcnMgPSA1KSAgICAgICAgICAgICAgICAgICAjIGhvdyBtYW55IGRpc3RpbmN0IHR1bW9yIHByb2ZpbGVzIHRvIGFwcGVuZCB0byBzYWZlVE1FDQoNCiNzdHIocERhdGEocmVzdGlscykpDQpoZWF0bWFwKHN3ZWVwKHJlc3RpbHNAZXhwZXJpbWVudERhdGFAb3RoZXIkU3BhdGlhbERlY29uTWF0cml4LCAxLCBhcHBseShyZXN0aWxzQGV4cGVyaW1lbnREYXRhQG90aGVyJFNwYXRpYWxEZWNvbk1hdHJpeCwgMSwgbWF4KSwgIi8iKSwNCiAgICAgICAgIGxhYlJvdyA9IE5BLCBtYXJnaW5zID0gYygxMCwgNSkpDQoNCmBgYA0KDQojIDkuMy4yIFBsb3R0aW5nIGRlY29udm9sdXRpb24gcmVzdWx0cw0KDQpgYGB7ciwgZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9N30NCiMgRm9yIHJlZmVyZW5jZSwgc2hvdyB0aGUgVElMcyBjb2xvciBkYXRhIG9iamVjdCB1c2VkIGJ5IHRoZSBwbG90dGluZyBmdW5jdGlvbnMgDQojIHdoZW4gc2FmZVRNRSBoYXMgYmVlbiB1c2VkOg0KZGF0YSgiY2VsbGNvbHMiKQ0KY2VsbGNvbHMNCg0KbyA9IGhjbHVzdChkaXN0KHQocmVzJGNlbGwuY291bnRzJGNlbGwuY291bnRzKSkpJG9yZGVyDQpsYXlvdXQobWF0cml4KGMoMSwgMiksIDEpLCB3aWR0aHMgPSBjKDcsIDMpKQ0KVElMX2JhcnBsb3QodChyZXMkY2VsbC5jb3VudHMkY2VsbC5jb3VudHNbLCBvXSksDQogICAgICAgICAgICBob3JpeiA9IFRSVUUsIGRyYXdfbGVnZW5kID0gVFJVRSwgY2V4Lm5hbWVzID0gMC45KQ0KDQojcGFyKG1hcj1jKDIsIDE1LCAyLCAyKSkNCg0KIyBvciB0aGUgcHJvcG9ydGlvbnMgb2YgY2VsbHM6DQp0ZW1wID0gcmVwbGFjZShyZXMkcHJvcF9vZl9ub250dW1vciwgaXMubmEocmVzJHByb3Bfb2Zfbm9udHVtb3IpLCAwKQ0KbyA9IGhjbHVzdChkaXN0KHRlbXBbcmVzJEFOTjIgPT0gIkNENDUiLF0pKSRvcmRlcg0KVElMX2JhcnBsb3QodChyZXMkcHJvcF9vZl9hbGwpLCANCiAgICAgICAgICAgIGhvcml6ID0gVFJVRSwgZHJhd19sZWdlbmQgPSBUUlVFLCBjZXgubmFtZXMgPSAwLjkpDQoNCmBgYA0KDQojIDkuMy4zIEZsb3JldHMgb2YgU3BhdGlhbCBkZWNvbnZvbHV0aW9uDQoNClRoZSBzZWNvbmQgZnVuY3Rpb24gaXMgImZsb3JldHMiLCB1c2VkIGZvciBwbG90dGluZyBjZWxsIGFidW5kYW5jZXMgYXRvcA0Kc29tZSAyLUQgcHJvamVjdGlvbi4gSGVyZSwgd2UnbGwgcGxvdCBjZWxsIGFidW5kYW5jZXMgYXRvcCB0aGUgZmlyc3QgMg0KcHJpbmNpcGFsIGNvbXBvbmVudHMgb2YgdGhlIGRhdGE6DQoNCmBgYHtyfQ0KDQojTk9URTogc2VlbXMgdG8gY3Jhc2ggaWYgYWxsIGFzc2lnbm1lbnRzIGFyZSB6ZXJvcyBpbiBhbiBBT0k/DQoNCiMgUENBIG9mIHRoZSBub3JtYWxpemVkIGRhdGE6DQpub3JtPWFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGEsZWx0PSJxX25vcm0iKQ0KcGMgPSBwcmNvbXAodChsb2cyKHBtYXgobm9ybSwgMSkpKSkkeFssIGMoMSwgMildDQoNCiMgDQojICMgcnVuIGZsb3JldHMgZnVuY3Rpb246DQpwYXIobWFyID0gYyg1LDUsMSwxKSkNCmxheW91dChtYXRyaXgoYygxLCAyKSwgMSksIHdpZHRocyA9IGMoNiwgMikpDQpmbG9yZXRzKHggPSBwY1sxOjE0LCAxXSwgeSA9IHBjWzE6MTQsIDJdLCBiID0gdChyZXMkYmV0YSlbMToxNCxdLCBjZXggPSAyLCB4bGFiID0gIlBDMSIsIHlsYWIgPSAiUEMyIikNCnBhcihtYXIgPSBjKDAsMCwwLDApKQ0KZnJhbWUoKQ0KbGVnZW5kKCJjZW50ZXIiLCBmaWxsID0gY2VsbGNvbHNbcm93bmFtZXModChyZXMkYmV0YSkpXSwNCiAgICAgICBsZWdlbmQgPSByb3duYW1lcyh0KHJlcyRiZXRhKSksIGNleCA9IDAuNykNCmBgYA0KDQojIDkuNCBjb21iaW5pbmcgY2VsbHR5cGVzDQoNCldoZW4gdHdvIGNlbGwgdHlwZXMgYXJlIHRvbyBzaW1pbGFyLCB0aGUgZXN0aW1hdGlvbiBvZiB0aGVpciBhYnVuZGFuY2VzDQpiZWNvbWVzIHVuc3RhYmxlLiBIb3dldmVyLCB0aGVpciBzdW0gY2FuIHN0aWxsIGJlIGVzdGltYXRlZCBlYXNpbHkuIFRoZQ0KZnVuY3Rpb24gImNvbGxhcHNlQ2VsbFR5cGVzIiB0YWtlcyBhIGRlY29udm9sdXRpb24gcmVzdWx0cyBvYmplY3QgYW5kDQpjb2xsYXBzZXMgYW55IGNvbHNlbHktcmVsYXRlZCBjZWxsIHR5cGVzIHlvdSB0ZWxsIGl0IHRvDQoNCmBgYHtyfQ0KbWF0Y2hpbmcgPSBsaXN0KCkNCm1hdGNoaW5nJG15ZWxvaWQgPSBjKCAibWFjcm9waGFnZXMiLCAibW9ub2N5dGVzIiwgIm1EQ3MiKQ0KbWF0Y2hpbmckVC5OSyA9IGMoIkNENC5ULmNlbGxzIiwiQ0Q4LlQuY2VsbHMiLCAiVHJlZyIsICJOSyIpDQptYXRjaGluZyRCID0gYygiQiIpDQptYXRjaGluZyRtYXN0ID0gYygibWFzdCIpDQptYXRjaGluZyRuZXV0cm9waGlscyA9IGMoIm5ldXRyb3BoaWxzIikNCm1hdGNoaW5nJHN0cm9tYSA9IGMoImVuZG90aGVsaWFsLmNlbGxzIiwgImZpYnJvYmxhc3RzIikNCg0KY29sbGFwc2VkID0gY29sbGFwc2VDZWxsVHlwZXMoZml0ID0gcmVzdGlscywgbWF0Y2hpbmcgPSBtYXRjaGluZykNCg0KaGVhdG1hcChjb2xsYXBzZWQkYmV0YSwgY2V4Um93ID0gMC44NSwgY2V4Q29sID0gMC43NSkNCmBgYA0K